JavaScript to Python cheatsheet
Everything a JavaScript developer needs to write Python, as side-by-side code. Search for any JavaScript concept — map, Object.keys, JSON.stringify, Promise.all, try/catch — and get the Python equivalent with the gotcha that comes with it.
Basics & Mental Model
The first hour of writing Python as a JavaScript developer: printing, comments, blocks and running a file.
| JavaScript | Python | Notes |
|---|---|---|
console.log(x) | print(x) | print takes many args: print(a, b, c) |
console.log(a, b) | print(a, b) | space-separated by default; sep="" to change |
console.error(x) | import sys; print(x, file=sys.stderr) | or use the logging module |
console.table(rows) | import pprint; pprint.pp(rows) | pandas DataFrame for real tables |
// comment | # comment | |
/* block comment */ | """block comment""" | a bare string; also used as a docstring |
{ ... } blocks | indentation (4 spaces) | indentation IS syntax — no braces anywhere |
statement; | statement | no semicolons; newline ends a statement |
line continuation with ( ) | \ at end of line, or wrap in ( ) | parentheses are the idiomatic way |
node app.js | python app.py | python3 app.py on older macOS/Linux setups |
node (REPL) | python (REPL) | or ipython for a much nicer REPL |
npx tsx watch app.ts | uv run --watch app.py | or watchfiles / nodemon-style tools |
camelCase names | snake_case names | PEP 8: functions/vars snake_case, classes PascalCase |
const MAX = 10 | MAX = 10 | UPPER_CASE is a convention only — nothing is enforced |
'use strict' | (always strict) | Python has no sloppy mode |
debugger | breakpoint() | drops into pdb |
process.exit(1) | import sys; sys.exit(1) | |
if (require.main === module) | if __name__ == "__main__": | the script-vs-import guard |
Variables & Primitive Types
No let/const/var. No declaration keyword at all — assignment creates the binding.
| JavaScript | Python | Notes |
|---|---|---|
let x = 0 | x = 0 | no keyword, no semicolon |
const y = 1 | y = 1 | no real constants; UPPER_CASE signals intent |
var z = 2 | z = 2 | Python scoping is function-level, no hoisting of values |
let a, b; a = b = 1 | a = b = 1 | |
[a, b] = [b, a] | a, b = b, a | tuple swap — no temp variable |
null / undefined | None | Python has exactly one 'nothing' value |
true / false | True / False | capitalised |
"text" / 'text' | "text" / 'text' | both quote styles identical |
`multi\nline` | """multi
line""" | triple quotes preserve newlines |
42, 3.14 | 42, 3.14 | int has UNLIMITED precision — 2 ** 1000 just works |
9007199254740993n (BigInt) | 9007199254740993 | every Python int is a bigint |
0.1 + 0.2 // 0.30000000000000004 | 0.1 + 0.2 # same float pain | use decimal.Decimal for money |
typeof x | type(x) | returns the class: <class 'int'> |
typeof x === "string" | isinstance(x, str) | isinstance is the idiomatic check |
x === undefined | x is None | is compares identity, == compares value |
x === null | x is None | |
Number.MAX_SAFE_INTEGER | (no limit for int) | sys.float_info.max for floats |
NaN | float("nan") | math.nan; check with math.isnan(x) |
Infinity | float("inf") | math.inf |
Symbol() | (no equivalent) | use unique sentinel objects: SENTINEL = object() |
let s = `Hi ${name}` | s = f"Hi {name}" | f-strings — no $, just braces |
Type Conversion & Coercion
JavaScript guesses. Python refuses and raises TypeError. You convert explicitly, always.
| JavaScript | Python | Notes |
|---|---|---|
"5" + 3 // "53" | "5" + str(3) | mixing str and int raises TypeError |
"5" - 3 // 2 | int("5") - 3 | no implicit string→number |
parseInt("42") | int("42") | int("42px") raises ValueError — parseInt would give 42 |
parseInt("ff", 16) | int("ff", 16) | base as second arg |
parseFloat("3.14") | float("3.14") | |
String(42) | str(42) | |
(42).toString(2) | bin(42) # '0b101010' | format(42, 'b') for no prefix |
Number(x) | int(x) / float(x) | pick the target type yourself |
Boolean(x) | bool(x) | |
+x (unary plus) | int(x) | no coercion operators |
x.toFixed(2) | f"{x:.2f}" | or round(x, 2) — returns a float, not a string |
JSON.parse(s) | json.loads(s) | import json |
JSON.stringify(o) | json.dumps(o) | json.dumps(o, indent=2) to pretty-print |
Array.from(s) | list(s) | |
Object.fromEntries(pairs) | dict(pairs) | |
[...str] | list(str) | splits into characters |
Truthiness & Equality
The two biggest trip-ups: empty containers are falsy in Python, and NaN is truthy.
| JavaScript | Python | Notes |
|---|---|---|
0 → falsy | 0 → falsy | same |
"" → falsy | "" → falsy | same |
null / undefined → falsy | None → falsy | same |
NaN → FALSY | float("nan") → TRUTHY | classic gotcha — use math.isnan() |
[] → TRUTHY | [] → FALSY | empty list is falsy in Python |
{} → TRUTHY | {} → FALSY | empty dict/set/tuple are all falsy |
arr.length === 0 | not arr | idiomatic emptiness check |
arr.length > 0 | if arr: | |
=== / !== | == / != | no === ; == already compares by value + type |
Object.is(a, b) | a is b | identity, not value |
a == b (loose) | (no equivalent) | Python has no loose equality — good riddance |
[1,2] === [1,2] // false | [1,2] == [1,2] # True | Python compares contents |
JSON.stringify(a)===JSON.stringify(b) | a == b | deep equality is built in |
a === b (reference) | a is b | use `is` only for None/True/False/sentinels |
1 < 2 && 2 < 3 | 1 < 2 < 3 | Python chains comparisons |
Operators
Words instead of symbols for logic; a real integer-division and power operator.
| JavaScript | Python | Notes |
|---|---|---|
&& / || / ! | and / or / not | keywords, not symbols |
a ? b : c | b if a else c | value first, condition in the middle |
x ?? y | y if x is None else x | no nullish operator |
x || y (default) | x or y | same falsy-based fallback |
x ||= y | x = x or y | no logical assignment operators |
x?.y | (no equivalent) | use d.get("y") or a try/except |
x?.() | (no equivalent) | if callable(x): x() |
a ** b / Math.pow(a,b) | a ** b | same operator |
Math.floor(a / b) | a // b | floor division (careful with negatives) |
a % b | a % b | Python's % follows the sign of the divisor: -7 % 3 == 2 |
x++ / x-- | x += 1 / x -= 1 | no increment operators |
a & b, a | b, a ^ b, ~a, <<, >> | same | bitwise operators are identical |
arr.includes(x) | x in arr | `in` works on lists, strings, dicts, sets |
!arr.includes(x) | x not in arr | |
a instanceof B | isinstance(a, B) | |
'k' in obj | "k" in obj | dict membership checks keys |
void 0 | None | |
(no equivalent) | x := f() (walrus) | assign inside an expression |
[...a, ...b] | [*a, *b] | spread |
{...a, ...b} | {**a, **b} | dict merge; also a | b (3.9+) |
Strings
Python strings are sequences: slicing, multiplication and `in` all work.
| JavaScript | Python | Notes |
|---|---|---|
s.length | len(s) | a function, not a property |
s[0] / s.charAt(0) | s[0] | |
s.at(-1) | s[-1] | negative indexing is native |
s.slice(0, 3) | s[0:3] # or s[:3] | |
s.slice(-3) | s[-3:] | |
s.split('').reverse().join('') | s[::-1] | slice with a step of -1 |
s.toUpperCase() / toLowerCase() | s.upper() / s.lower() | also s.casefold() for comparisons |
s[0].toUpperCase()+s.slice(1) | s.capitalize() | s.title() for Title Case |
s.trim() / trimStart() / trimEnd() | s.strip() / lstrip() / rstrip() | can strip specific chars: s.strip("/") |
s.includes('a') | "a" in s | |
s.indexOf('a') | s.find("a") | find returns -1; index() raises ValueError |
s.lastIndexOf('a') | s.rfind("a") | |
s.startsWith('a') / endsWith | s.startswith("a") / s.endswith("a") | accepts a tuple of options |
s.replace('a','b') // first only | s.replace("a", "b") # ALL | s.replace("a","b",1) for first only |
s.replaceAll('a','b') | s.replace("a", "b") | |
s.split(',') | s.split(",") | s.split() with no args splits on any whitespace |
s.split('\n') | s.splitlines() | handles \r\n too |
arr.join(',') | ",".join(arr) | separator first — items must be strings |
s.repeat(3) | s * 3 | |
s.padStart(5,'0') | s.zfill(5) # or s.rjust(5, "0") | ljust / center too |
s.charCodeAt(0) | ord(s[0]) | ord(char) → int codepoint |
String.fromCharCode(97) | chr(97) | |
s.match(/re/) | re.search(r"re", s) | import re |
s.normalize() | unicodedata.normalize("NFC", s) | |
[...s] | list(s) | |
s.concat(t) | s + t | |
Array.from(s).every(c=>...) | all(... for c in s) | s.isdigit(), s.isalpha(), s.isupper() built in |
String Formatting & Templates
f-strings replace template literals — and do far more.
| JavaScript | Python | Notes |
|---|---|---|
`Hi ${name}` | f"Hi {name}" | the default choice |
`${a + b}` | f"{a + b}" | any expression inside the braces |
`${obj.x}` | f"{obj.x}" | attribute access works |
(no equivalent) | f"{value=}" | debug syntax prints `value=42` |
x.toFixed(2) | f"{x:.2f}" | format spec after a colon |
n.toLocaleString() | f"{n:,}" | thousands separator |
(pct*100).toFixed(1)+'%' | f"{pct:.1%}" | percent formatting built in |
s.padStart(10) | f"{s:>10}" | < left, ^ center, > right |
util.format / sprintf | "%s is %d" % (a, b) | old-style; still common in logging |
template literal tag fn | "{} and {}".format(a, b) | str.format is the middle-generation API |
String.raw`\n` | r"\n" | raw strings — essential for regex |
JSON.stringify(o, null, 2) | json.dumps(o, indent=2) | |
(no equivalent) | from string import Template | safe user-supplied templates |
date.toISOString() | dt.isoformat() | |
Intl.DateTimeFormat | dt.strftime("%Y-%m-%d") |
Numbers & Math
Most Math.* helpers are builtins or live in the math module.
| JavaScript | Python | Notes |
|---|---|---|
Math.floor(x) | math.floor(x) | import math |
Math.ceil(x) | math.ceil(x) | |
Math.round(x) | round(x) | banker's rounding: round(0.5) == 0 |
Math.abs(x) | abs(x) | builtin |
Math.max(a,b) / min | max(a,b) / min(a,b) | max(list) works directly |
Math.max(...arr) | max(arr) | |
Math.pow(a,b) | a ** b | |
Math.sqrt(x) | math.sqrt(x) | or x ** 0.5 |
Math.trunc(x) | math.trunc(x) / int(x) | |
Math.sign(x) | (x > 0) - (x < 0) | no built-in sign |
Math.random() | random.random() | import random |
Math.floor(Math.random()*n) | random.randrange(n) | random.randint(a,b) is inclusive |
arr[Math.floor(Math.random()*len)] | random.choice(arr) | random.sample / random.shuffle too |
Math.PI / Math.E | math.pi / math.e | |
Math.log(x) / log2 / log10 | math.log(x) / math.log2 / math.log10 | |
Math.hypot(a,b) | math.hypot(a,b) | |
Number.isInteger(x) | isinstance(x, int) | or float.is_integer() |
isNaN(x) | math.isnan(x) | |
arr.reduce((a,b)=>a+b,0) | sum(arr) | builtin |
(no equivalent) | divmod(a, b) | returns (quotient, remainder) |
(no equivalent) | from decimal import Decimal | exact decimal arithmetic for money |
(no equivalent) | from fractions import Fraction | rational numbers |
(no equivalent) | import statistics | mean, median, stdev in the stdlib |
Arrays → Lists
Lists are mutable sequences. Slicing replaces half the array API.
| JavaScript | Python | Notes |
|---|---|---|
const a = [1,2,3] | a = [1, 2, 3] | |
new Array(3).fill(0) | [0] * 3 | careful: [[]]*3 shares the same inner list |
Array.from({length:n},(_,i)=>i) | list(range(n)) | |
a.length | len(a) | |
a[10] // undefined | a[10] # IndexError | Python raises instead of returning undefined |
a.at(-1) | a[-1] | |
a.push(x) | a.append(x) | |
a.push(...b) | a.extend(b) # or a += b | |
a.pop() | a.pop() | |
a.shift() | a.pop(0) | use collections.deque for O(1) |
a.unshift(x) | a.insert(0, x) | |
a.splice(i, 1) | del a[i] # or a.pop(i) | |
a.splice(i, 0, x) | a.insert(i, x) | |
a.splice(i, 2, x, y) | a[i:i+2] = [x, y] | slice assignment |
a.slice(1, 3) | a[1:3] | |
a.slice() | a.copy() # or a[:] | shallow copy |
structuredClone(a) | copy.deepcopy(a) | import copy |
a.indexOf(x) | a.index(x) | raises ValueError if missing |
a.includes(x) | x in a | |
a.concat(b) | a + b | |
[...a] | [*a] | |
a.flat() | [x for sub in a for x in sub] | or itertools.chain.from_iterable(a) |
a.fill(0) | a[:] = [0] * len(a) | |
a.length = 0 | a.clear() | |
a.reverse() | a.reverse() | in place; reversed(a) returns an iterator |
a.filter(Boolean) | [x for x in a if x] | |
a.join('') | "".join(map(str, a)) | items must be strings |
Array.isArray(a) | isinstance(a, list) | |
a.every(f) | all(f(x) for x in a) | |
a.some(f) | any(f(x) for x in a) | |
a.find(f) | next((x for x in a if f(x)), None) | |
a.findIndex(f) | next((i for i,x in enumerate(a) if f(x)), -1) | |
a.count / manual | a.count(x) | counts occurrences |
(no equivalent) | a[::2] | every other item — slicing with a step |
(no equivalent) | a[::-1] | reversed copy |
Iteration: map / filter / reduce
Comprehensions are the idiomatic Python replacement for chained array methods.
| JavaScript | Python | Notes |
|---|---|---|
a.map(f) | [f(x) for x in a] | or list(map(f, a)) |
a.map(x => x * 2) | [x * 2 for x in a] | comprehension is preferred |
a.filter(f) | [x for x in a if f(x)] | or list(filter(f, a)) |
a.map(f).filter(g) | [f(x) for x in a if g(x)] | no chaining — one comprehension |
a.reduce(f, init) | functools.reduce(f, a, init) | from functools import reduce |
a.reduce((s,x)=>s+x,0) | sum(a) | prefer the builtin |
a.forEach(x => ...) | for x in a: | no callback needed |
a.forEach((x,i) => ...) | for i, x in enumerate(a): | enumerate(a, start=1) to offset |
a.entries() | enumerate(a) | |
a.keys() | range(len(a)) | |
zip two arrays manually | for x, y in zip(a, b): | zip is a builtin |
(no equivalent) | itertools.zip_longest(a, b) | pads the shorter one |
a.flatMap(f) | [y for x in a for y in f(x)] | |
(no equivalent) | itertools.chain(a, b) | lazy concatenation |
(no equivalent) | itertools.groupby(sorted(a, key=k), key=k) | must sort first |
(no equivalent) | itertools.product(a, b) | cartesian product — nested loops flattened |
(no equivalent) | itertools.combinations(a, 2) | and permutations() |
a.slice(0, 5) on a stream | itertools.islice(gen, 5) | works on any iterator |
array.reverse() in loop | for x in reversed(a): | |
for (const x of new Set(a)) | for x in set(a): |
Sorting & Comparators
Python sorts with a key function, not a comparator — and sorts numbers correctly by default.
| JavaScript | Python | Notes |
|---|---|---|
a.sort() // lexicographic! | a.sort() | Python sorts numbers numerically |
a.sort((x,y)=>x-y) | a.sort() | |
a.sort((x,y)=>y-x) | a.sort(reverse=True) | |
[...a].sort() | sorted(a) | sorted returns a new list; .sort() mutates |
a.sort((x,y)=>x.age-y.age) | a.sort(key=lambda p: p.age) | key function, not comparator |
sort by two fields | a.sort(key=lambda p: (p.last, p.first)) | tuple key |
sort desc by one field | sorted(a, key=lambda p: -p.age) | or reverse=True |
a.sort((x,y)=>x.localeCompare(y)) | sorted(a, key=str.lower) | |
(no equivalent) | from operator import itemgetter, attrgetter | faster than lambdas |
(no equivalent) | functools.cmp_to_key(cmp) | when you really need a comparator |
Math.min(...a.map(f)) | min(a, key=f) | max(a, key=f) too |
a.sort().slice(0,3) | heapq.nsmallest(3, a) | faster for top-N |
(no equivalent) | heapq.nlargest(3, a, key=f) | |
(stable since ES2019) | (always stable) | Timsort — stable by guarantee |
binary search manually | bisect.bisect_left(a, x) | on a sorted list |
Objects → Dictionaries
Dicts have no dot access, keys are real values (not just strings), and missing keys raise.
| JavaScript | Python | Notes |
|---|---|---|
const o = {a: 1} | o = {"a": 1} | string keys must be quoted |
{ [key]: v } | {key: v} | keys are ordinary expressions |
o.a | o["a"] | no dot access on dicts |
o.missing // undefined | o["missing"] # KeyError | |
o.x ?? def | o.get("x", def) | .get() returns None by default |
delete o.a | del o["a"] | o.pop("a", None) to delete safely |
'a' in o | "a" in o | checks keys |
Object.keys(o) | o.keys() | wrap in list() if you need indexing |
Object.values(o) | o.values() | |
Object.entries(o) | o.items() | |
for (const [k,v] of Object.entries(o)) | for k, v in o.items(): | |
for (const k in o) | for k in o: | iterating a dict yields keys |
Object.assign({}, a, b) | {**a, **b} | or a | b in Python 3.9+ |
Object.assign(a, b) | a.update(b) | in place; a |= b in 3.9+ |
{...o} | o.copy() # or dict(o) | |
Object.freeze(o) | types.MappingProxyType(o) | read-only view |
Object.keys(o).length | len(o) | |
Object.fromEntries(pairs) | dict(pairs) | |
Object.entries(o).map(...) | {k: f(v) for k, v in o.items()} | dict comprehension |
const {a, b} = o | a, b = o["a"], o["b"] | no object destructuring |
function f({a, b}) | def f(*, a, b): | call as f(**o) to spread a dict |
f(...args) | f(*args) | positional spread |
f({...opts}) | f(**opts) | keyword spread |
o[k] ??= [] | o.setdefault(k, []).append(x) | or collections.defaultdict(list) |
Map | dict | dict preserves insertion order (3.7+) |
WeakMap | weakref.WeakKeyDictionary | |
nested optional chaining o?.a?.b | o.get("a", {}).get("b") | no ?. operator |
Sets, Tuples & Immutability
Python gives you first-class set algebra and a real immutable sequence.
| JavaScript | Python | Notes |
|---|---|---|
new Set([1,2]) | {1, 2} # or set([1,2]) | {} alone is an empty dict — use set() |
s.add(x) | s.add(x) | |
s.has(x) | x in s | |
s.delete(x) | s.discard(x) | s.remove(x) raises if missing |
s.size | len(s) | |
[...new Set(a)] | list(set(a)) | dedupe — order not preserved |
dedupe keeping order | list(dict.fromkeys(a)) | classic trick |
union (manual) | a | b # a.union(b) | |
intersection (manual) | a & b | |
difference (manual) | a - b | |
symmetric difference | a ^ b | |
subset check (manual) | a <= b | a < b for proper subset |
(use an array) | t = (1, 2, 3) | tuple — immutable, hashable |
[1] | (1,) | single-element tuple needs the trailing comma |
const [a, b] = pair | a, b = pair | tuple unpacking |
const [a, ...rest] = arr | a, *rest = arr | starred unpacking |
Object.freeze([1,2]) | (1, 2) | tuples are genuinely immutable |
(no equivalent) | frozenset({1,2}) | hashable set — usable as a dict key |
(no equivalent) | from collections import namedtuple | lightweight record type |
(no equivalent) | from typing import NamedTuple | typed, class-style namedtuple |
Control Flow
elif, match/case, and for-else — plus no do/while.
| JavaScript | Python | Notes |
|---|---|---|
if (c) { } | if c: | colon + indented block |
else if (d) { } | elif d: | |
else { } | else: | |
if (a && b) | if a and b: | |
switch (x) { case 1: ... } | match x:
case 1: | Python 3.10+ |
default: | case _: | wildcard pattern |
switch fallthrough | (none — no fallthrough) | no break needed |
(no equivalent) | case {'type': t, **rest}: | structural pattern matching destructures |
(no equivalent) | case Point(x=0, y=y): | class patterns |
case 1: case 2: | case 1 | 2: | or-patterns |
while (c) { } | while c: | |
do { } while (c) | while True:
...
if not c: break | no do/while |
for (let i=0;i<n;i++) | for i in range(n): | |
for (let i=n-1;i>=0;i--) | for i in range(n-1, -1, -1): | or reversed(range(n)) |
for (const v of arr) | for v in arr: | |
for (const [i,v] of arr.entries()) | for i, v in enumerate(arr): | |
break / continue | break / continue | same |
labelled break | (no labels) | refactor into a function and return |
(no equivalent) | for x in a:
...
else:
... | else runs if the loop wasn't broken |
empty block { } | pass | pass is the no-op statement |
throw new Error('TODO') | raise NotImplementedError | |
(no equivalent) | assert cond, "message" | stripped when running with -O |
Functions
Real keyword arguments, real default values — and lambdas limited to a single expression.
| JavaScript | Python | Notes |
|---|---|---|
function f(a, b) { } | def f(a, b): | |
const f = (x) => x * x | f = lambda x: x * x | or just def f(x): return x * x |
const f = x => { ...; return y } | def f(x): ... | lambdas hold ONE expression only |
function f() { } | def f():
pass | returns None implicitly |
return | return | bare return → None |
f(a = 1) | def f(a=1): | defaults evaluated ONCE at def time |
default = [] | def f(a=None):
a = a or [] | mutable default arg is a classic bug |
function f(...args) | def f(*args): | args is a tuple |
f(...arr) | f(*arr) | |
function f({a, b}) | def f(**kwargs): | kwargs is a dict |
f({a: 1}) | f(a=1) | real named arguments |
(no equivalent) | def f(a, *, b): | b is keyword-only |
(no equivalent) | def f(a, b, /): | a, b are positional-only |
arguments object | *args | no implicit arguments |
fn.length | len(inspect.signature(f).parameters) | |
f.call(obj) / apply / bind | f(obj) / functools.partial(f, obj) | no `this` to rebind |
const g = f.bind(null, 1) | g = functools.partial(f, 1) | |
higher-order wrap(fn) | @decorator | decorator syntax |
memoize(fn) | @functools.cache | or @lru_cache(maxsize=128) |
typeof f === 'function' | callable(f) | |
IIFE (() => {})() | (lambda: ...)() | rarely used — just call a def |
function* gen() { yield 1 } | def gen():
yield 1 | generators |
gen().next().value | next(g) | raises StopIteration when exhausted |
yield* | yield from |
Scope, Closures & `this`
No hoisting drama, no `this` binding — but you must declare intent to rebind.
| JavaScript | Python | Notes |
|---|---|---|
block scoping with let | function scoping | loops/ifs do NOT create a scope |
closures capture variables | closures capture variables | same idea |
outer = 1 inside a fn | nonlocal outer | needed to REASSIGN an enclosing var |
global var assignment | global x | needed to reassign a module-level var |
reading an outer var | (just read it) | no keyword needed to read |
this (dynamic) | self (explicit param) | self is never implicit |
arrow fn lexical this | (not needed) | self is passed explicitly |
hoisting (var/function) | (no hoisting) | NameError if used before assignment |
TDZ ReferenceError | UnboundLocalError | assigning later in a fn shadows the global |
module scope is isolated | module scope is isolated | each .py file is a module |
globalThis | globals() | locals() too |
setTimeout closure-in-loop bug | same late-binding bug | fix with a default arg: lambda x=x: ... |
Comprehensions & Generators
The single most Pythonic construct — worth learning on day one.
| JavaScript | Python | Notes |
|---|---|---|
a.map(f) | [f(x) for x in a] | list comprehension |
a.filter(g).map(f) | [f(x) for x in a if g(x)] | filter clause goes last |
a.map(x => cond ? p : q) | [p if cond else q for x in a] | ternary inside the expression |
nested loops building an array | [(x,y) for x in a for y in b] | outer loop first |
Object.fromEntries(...) | {k: v for k, v in pairs} | dict comprehension |
new Set(a.map(f)) | {f(x) for x in a} | set comprehension |
lazy iterable (generator) | (f(x) for x in a) | generator expression — parentheses |
sum of a big stream | sum(x*x for x in a) | no intermediate list allocated |
function* g(){} | def g():
yield x | generator function |
iterator protocol | __iter__ / __next__ | or just use yield |
for await (const x of stream) | async for x in stream: | async generators |
(no equivalent) | yield from other_gen() | delegate to another generator |
(no equivalent) | itertools.count() / cycle() / repeat() | infinite iterators |
(no equivalent) | itertools.accumulate(a) | running totals |
(no equivalent) | itertools.takewhile / dropwhile |
Classes & OOP
Explicit self, no `new`, real multiple inheritance and properties.
| JavaScript | Python | Notes |
|---|---|---|
class Book { } | class Book: | |
constructor(t) { } | def __init__(self, t): | __new__ is the actual allocator |
this.title = t | self.title = t | self is the explicit first parameter |
new Book('x') | Book("x") | no `new` keyword |
describe() { } | def describe(self): | every method takes self |
static m() { } | @staticmethod
def m(): | |
static factory | @classmethod
def from_json(cls, d): | cls is the class itself |
get area() { } | @property
def area(self): | |
set area(v) { } | @area.setter
def area(self, v): | |
class Sub extends Base { } | class Sub(Base): | |
super(t) | super().__init__(t) | must call it explicitly |
super.method() | super().method() | |
(single inheritance) | class C(A, B): | multiple inheritance + MRO |
#private field | self.__x | name mangling, not true privacy |
_convention | self._x | single underscore = 'internal, please don't touch' |
toString() | __str__ / __repr__ | repr is for developers, str for users |
Symbol.iterator | __iter__ | |
valueOf() | __int__ / __float__ | |
instanceof | isinstance(obj, C) | |
obj.constructor.name | type(obj).__name__ | |
Object.getPrototypeOf | C.__mro__ | method resolution order |
abstract class (TS) | from abc import ABC, abstractmethod | |
interface (TS) | typing.Protocol | structural typing |
plain data class | @dataclass | auto __init__, __repr__, __eq__ |
(no equivalent) | __slots__ = ('x','y') | restrict attributes, save memory |
Object.defineProperty | __getattr__ / __setattr__ | attribute hooks |
enum (TS) | from enum import Enum |
Dunder / Magic Methods
Python lets your objects respond to operators, len(), `in` and more.
| JavaScript | Python | Notes |
|---|---|---|
toString() | __str__(self) | used by print() and str() |
console.log inspection | __repr__(self) | used by the REPL and debuggers |
a.equals(b) | __eq__(self, other) | powers == and != |
hash for a Map key | __hash__(self) | must match __eq__ |
a.length / a.size | __len__(self) | makes len(obj) work |
a[i] | __getitem__(self, i) | also enables iteration fallback |
a[i] = v | __setitem__(self, i, v) | |
x in a | __contains__(self, x) | |
a + b | __add__(self, other) | operator overloading is idiomatic |
a < b (comparator) | __lt__(self, other) | @total_ordering fills in the rest |
f() | __call__(self) | makes an instance callable |
for..of | __iter__ / __next__ | |
using / Symbol.dispose | __enter__ / __exit__ | the `with` protocol |
await using | __aenter__ / __aexit__ | async context managers |
await obj | __await__ | |
proxy get trap | __getattr__(self, name) | called only when normal lookup fails |
JSON.stringify hook (toJSON) | __dict__ / custom encoder | json.JSONEncoder subclass |
Symbol.toPrimitive | __bool__ / __int__ / __float__ |
Modules, Imports & Project Layout
Dots instead of slashes, and __init__.py instead of index.js.
| JavaScript | Python | Notes |
|---|---|---|
import lodash from "lodash" | import math | |
import { sqrt } from "./m" | from math import sqrt | |
import * as u from "./u" | import utils as u | |
import { x as y } from "m" | from m import x as y | |
export function f() { } | def f(): # everything is exported | __all__ = ["f"] limits `from m import *` |
export default | (no default export) | import the name you want |
index.js (barrel) | __init__.py | makes a folder a package |
"./sibling" | from .sibling import x | leading dot = relative import |
"../parent" | from ..parent import x | two dots = one level up |
require() dynamic | importlib.import_module(name) | |
top-level await in ESM | asyncio.run(main()) | |
package.json | pyproject.toml | PEP 621 metadata |
node_modules | .venv | virtual environment per project |
package-lock.json | uv.lock / poetry.lock / requirements.txt | |
.nvmrc | .python-version | |
NODE_PATH | PYTHONPATH | |
circular import warning | ImportError on circular imports | move the import inside the function |
module caching | sys.modules cache | same behaviour |
Tooling: npm → uv / pip
uv is the modern single tool — think npm + nvm + npx in one binary.
| JavaScript | Python | Notes |
|---|---|---|
npm init | uv init | creates pyproject.toml |
npm install | uv sync | installs from the lockfile |
npm install pkg | uv add pkg | pip install pkg for the classic way |
npm install -D pkg | uv add --dev pkg | |
npm uninstall pkg | uv remove pkg | |
npm run script | uv run script | scripts live in pyproject.toml |
npx tool | uvx tool | run without installing |
nvm install 20 | uv python install 3.12 | uv manages interpreters too |
npm ls | uv pip list | |
npm outdated | uv lock --upgrade | |
yarn / pnpm | poetry / pipenv / pdm | older alternatives to uv |
node_modules/.bin | .venv/bin | activate with source .venv/bin/activate |
eslint | ruff | ruff check — extremely fast linter |
prettier | ruff format / black | |
typescript / tsc | mypy / pyright | static type checkers |
jest / vitest | pytest | |
husky | pre-commit | git hook manager |
npm publish | uv build && uv publish | to PyPI |
semantic-release | hatch / bump-my-version | |
(no equivalent) | conda / miniforge | for ML stacks with native/CUDA binaries |
Errors & Exceptions
except instead of catch — and an else clause you'll actually use.
| JavaScript | Python | Notes |
|---|---|---|
try { } catch (e) { } | try:
...
except Exception as e: | |
finally { } | finally: | same semantics |
catch { } (no binding) | except Exception: | |
throw new Error('x') | raise Exception("x") | prefer a specific class |
throw new TypeError(...) | raise TypeError(...) | |
throw err (rethrow) | raise | bare raise re-raises the current exception |
if (e instanceof MyErr) | except MyErr as e: | list specific excepts first |
class E extends Error { } | class E(Exception): pass | |
error.message | str(e) | e.args holds the raw arguments |
error.stack | traceback.format_exc() | import traceback |
error.cause | raise New() from err | explicit exception chaining |
(no equivalent) | try/except/else: | else runs only when nothing raised |
catch and ignore | except SomeError:
pass | never bare `except:` |
Promise.allSettled errors | asyncio.gather(..., return_exceptions=True) | |
process.on('uncaughtException') | sys.excepthook | |
AggregateError | ExceptionGroup / except* | Python 3.11+ |
custom error codes | raise ValueError / KeyError / TypeError | use the built-in hierarchy |
undefined is not a function | AttributeError / TypeError | the equivalent runtime blow-up |
arr[99] → undefined | IndexError | Python fails loudly |
obj.missing → undefined | KeyError / AttributeError |
Async & Concurrency
Same async/await keywords — but coroutines don't start until you run them on a loop.
| JavaScript | Python | Notes |
|---|---|---|
async function f() { } | async def f(): | |
await p | await p | only valid inside async def |
f() starts immediately | f() returns a coroutine | nothing runs until awaited/scheduled |
f().then(...) | asyncio.run(f()) | the entry point for an async program |
Promise | Awaitable / Task / Future | |
new Promise(res => ...) | asyncio.Future() | rarely needed |
Promise.resolve(v) | async def f(): return v | |
setTimeout(fn, 1000) | await asyncio.sleep(1) | seconds, not milliseconds |
setTimeout without await | asyncio.create_task(coro()) | fire-and-forget — keep a reference |
Promise.all([a, b]) | await asyncio.gather(a, b) | returns results in order |
Promise.allSettled | asyncio.gather(*t, return_exceptions=True) | |
Promise.race | asyncio.wait(t, return_when=asyncio.FIRST_COMPLETED) | |
Promise.any | (compose with asyncio.wait) | |
AbortController | task.cancel() | raises CancelledError inside the coroutine |
timeout wrapper | async with asyncio.timeout(5): | Python 3.11+ |
for await (const x of s) | async for x in s: | async iteration |
(no equivalent) | async with | async context managers |
worker_threads | threading / concurrent.futures | GIL limits CPU-bound threads |
cluster / child_process | multiprocessing | true parallelism for CPU work |
p-limit | asyncio.Semaphore(n) | bound concurrency |
queueMicrotask | loop.call_soon | |
event loop | asyncio event loop | same single-threaded model |
(no equivalent) | async with asyncio.TaskGroup() as tg: | structured concurrency, 3.11+ |
Dates & Time
datetime is the stdlib answer; use timezone-aware objects.
| JavaScript | Python | Notes |
|---|---|---|
new Date() | datetime.now() | from datetime import datetime |
new Date().toISOString() | datetime.now(timezone.utc).isoformat() | always attach a tz |
Date.now() | time.time() | seconds as a float, not ms |
performance.now() | time.perf_counter() | monotonic clock for benchmarks |
new Date(2024, 0, 1) | datetime(2024, 1, 1) | months are 1-based in Python |
new Date(str) | datetime.fromisoformat(str) | or dateutil.parser.parse |
d.getFullYear() | d.year | plain attributes, not getters |
d.getTime() | d.timestamp() | seconds |
date-fns addDays(d, 3) | d + timedelta(days=3) | arithmetic with timedelta |
diff in ms | (a - b).total_seconds() | subtraction gives a timedelta |
date-fns format | d.strftime("%Y-%m-%d %H:%M") | |
parse custom format | datetime.strptime(s, "%d/%m/%Y") | |
Intl / moment-timezone | from zoneinfo import ZoneInfo | ZoneInfo("Europe/Berlin") |
sleep via setTimeout | time.sleep(1) | blocking; await asyncio.sleep in async code |
cron (node-cron) | APScheduler / cron | |
(no equivalent) | datetime.date / datetime.time | date-only and time-only types |
Regular Expressions
No literal syntax — patterns are raw strings passed to the `re` module.
| JavaScript | Python | Notes |
|---|---|---|
/abc/ | r"abc" | the r prefix stops backslash escaping |
/abc/i | re.IGNORECASE flag | re.search(p, s, re.I) |
re.test(s) | re.search(p, s) is not None | search scans anywhere |
s.match(/^a/) | re.match(p, s) | match only anchors at the start |
s.match(re)[1] | m.group(1) | m[1] works too |
named groups (?<x>...) | (?P<x>...) | m.group("x") |
s.matchAll(/re/g) | re.finditer(p, s) | re.findall for just the strings |
s.replace(/re/g, 'x') | re.sub(p, "x", s) | |
s.replace(re, fn) | re.sub(p, fn, s) | fn receives the match object |
s.split(/\s+/) | re.split(r"\s+", s) | |
new RegExp(str) | re.compile(str) | compile once, reuse |
/re/g lastIndex | (stateless) | no global-flag statefulness |
lookahead (?=) | (?=) | same syntax; lookbehind must be fixed-width |
\d \w \s | \d \w \s | Unicode-aware by default in Python 3 |
JSON, Files & Paths
`with` blocks replace manual close(); pathlib replaces the path module.
| JavaScript | Python | Notes |
|---|---|---|
JSON.parse(s) | json.loads(s) | loads = load string |
JSON.stringify(o) | json.dumps(o) | dumps = dump string |
JSON.stringify(o, null, 2) | json.dumps(o, indent=2) | |
JSON.parse(fs.readFileSync(p)) | json.load(open(p)) | load/dump take file objects |
fs.readFileSync(p, "utf-8") | Path(p).read_text() | or open(p).read() |
fs.writeFileSync(p, data) | Path(p).write_text(data) | |
fs.createReadStream + lines | for line in open(p): | lazy, memory-friendly |
manual close() | with open(p) as fh: | context manager closes automatically |
fs.appendFileSync | open(p, "a").write(s) | mode 'a' |
fs.existsSync(p) | Path(p).exists() | |
fs.mkdirSync(p, {recursive:true}) | Path(p).mkdir(parents=True, exist_ok=True) | |
fs.readdirSync(p) | list(Path(p).iterdir()) | |
glob('**/*.js') | Path(".").rglob("*.py") | or the glob module |
fs.unlinkSync(p) | Path(p).unlink() | |
fs.rmSync(p, {recursive:true}) | shutil.rmtree(p) | |
fs.copyFileSync | shutil.copy(src, dst) | |
path.join(a, b) | Path(a) / b | the / operator joins paths |
path.extname(p) | Path(p).suffix | |
path.basename(p) | Path(p).name | .stem for the name without extension |
path.dirname(p) | Path(p).parent | |
path.resolve(p) | Path(p).resolve() | |
__dirname | Path(__file__).parent | |
__filename | __file__ | |
os.tmpdir() | tempfile.gettempdir() | tempfile.NamedTemporaryFile |
csv-parse | import csv | csv.DictReader in the stdlib |
yaml | import yaml # PyYAML | third-party but ubiquitous |
HTTP, APIs & Servers
requests for sync, httpx/aiohttp for async, FastAPI for serving.
| JavaScript | Python | Notes |
|---|---|---|
fetch(url) | requests.get(url) | pip install requests |
await fetch(url) | await httpx.AsyncClient().get(url) | httpx = async-capable requests |
res.json() | res.json() | a method in both |
res.ok / res.status | res.ok / res.status_code | |
fetch(url, {method:'POST', body}) | requests.post(url, json=body) | json= sets the header for you |
headers: { Authorization } | headers={"Authorization": ...} | |
axios instance | requests.Session() / httpx.Client() | connection pooling |
URLSearchParams | params={...} | or urllib.parse.urlencode |
new URL(u) | urllib.parse.urlparse(u) | |
encodeURIComponent(s) | urllib.parse.quote(s) | |
express / fastify | FastAPI | async, typed, auto OpenAPI docs |
app.get('/x', handler) | @app.get("/x")
async def handler(): | decorator routing |
zod validation | pydantic | FastAPI uses it for request models |
nodemon / ts-node-dev | uvicorn main:app --reload | |
ws / socket.io | websockets / FastAPI WebSocket | |
node-fetch retry libs | tenacity | retry decorators |
cheerio | beautifulsoup4 | HTML parsing |
puppeteer / playwright | playwright (python) | same API, Python bindings |
Testing & Debugging
pytest with plain assert — no expect().toBe() chains needed.
| JavaScript | Python | Notes |
|---|---|---|
describe / it | def test_thing(): | any function named test_* |
expect(a).toBe(b) | assert a == b | pytest rewrites asserts for good output |
expect(a).toEqual(obj) | assert a == obj | deep equality is native |
expect(fn).toThrow() | with pytest.raises(ValueError): | |
beforeEach | @pytest.fixture | fixtures are injected by parameter name |
test.each([...]) | @pytest.mark.parametrize(...) | |
jest.mock() | unittest.mock / monkeypatch | pytest's monkeypatch fixture |
jest.spyOn | mocker.patch (pytest-mock) | |
test.skip / only | @pytest.mark.skip / -k filter | |
npm test | pytest | pytest -q for quiet output |
--coverage | pytest --cov | pytest-cov |
console.log debugging | print() / breakpoint() | |
node --inspect | python -m pdb app.py | |
winston / pino | import logging | logging.getLogger(__name__) |
console.time | time.perf_counter() | or timeit / cProfile |
clinic / 0x profiler | cProfile + snakeviz | py-spy for live processes |
Type Hints (TypeScript → Python)
Optional, gradual, checked by mypy/pyright — never enforced at runtime.
| JavaScript | Python | Notes |
|---|---|---|
let x: number | x: int = 0 | annotations are optional everywhere |
function f(a: string): number | def f(a: str) -> int: | |
string[] | list[str] | List[str] on Python < 3.9 |
Record<string, number> | dict[str, int] | |
[string, number] | tuple[str, int] | |
A | B | A | B | Union[A, B] on older versions |
T | null | T | None | Optional[T] |
any | Any | from typing import Any |
unknown | object | |
never | NoReturn | |
interface User { } | class User(TypedDict): | or a Protocol / dataclass |
type alias | type UserId = int | Python 3.12 syntax; else UserId = int |
generics <T> | def f[T](x: T) -> T: | 3.12 syntax; else TypeVar |
as const / literal types | Literal["a", "b"] | |
readonly | Final | from typing import Final |
enum | from enum import Enum | |
zod runtime validation | pydantic BaseModel | validation actually enforced at runtime |
tsc --noEmit | mypy . / pyright |
Environment, CLI & Process
os and sys replace the process global; argparse replaces yargs.
| JavaScript | Python | Notes |
|---|---|---|
process.env.KEY | os.environ["KEY"] | import os |
process.env.KEY ?? 'x' | os.getenv("KEY", "x") | |
dotenv | from dotenv import load_dotenv | python-dotenv |
process.argv | sys.argv | same shape, argv[0] is the script |
yargs / commander | argparse | in the stdlib; typer/click are nicer |
process.cwd() | os.getcwd() | or Path.cwd() |
process.exit(0) | sys.exit(0) | |
process.platform | sys.platform | |
process.version | sys.version | |
child_process.execSync | subprocess.run([...], capture_output=True) | pass a list, not a string |
process.on('SIGINT') | signal.signal(signal.SIGINT, handler) | |
os.cpus().length | os.cpu_count() | |
readline question | input('Name: ') | builtin |
chalk | rich / colorama | rich also does tables, progress bars, markdown |
ora spinner | rich.progress / tqdm |
Standard Library (batteries included)
Much of what you'd npm install in Node already ships with Python.
| JavaScript | Python | Notes |
|---|---|---|
lodash.countBy | collections.Counter(a) | .most_common(3) for top-N |
lodash.groupBy | collections.defaultdict(list) | or itertools.groupby on sorted data |
deque / linked list pkg | collections.deque | O(1) appendleft/popleft |
lodash.chunk | itertools.batched(a, n) | Python 3.12+ |
lodash.uniq | list(dict.fromkeys(a)) | order-preserving dedupe |
lodash.zip | zip(a, b) | builtin |
lodash.range | range(n) | builtin |
lodash.partial | functools.partial | |
lodash.memoize | functools.cache | |
ramda pipe/compose | (no builtin) | chain comprehensions or use functools.reduce |
uuid | import uuid; uuid.uuid4() | |
crypto.createHash | hashlib.sha256(b).hexdigest() | |
crypto.randomBytes | secrets.token_hex(16) | use secrets, not random, for tokens |
base64 helpers | import base64 | |
querystring | urllib.parse | |
zlib / tar | gzip, zipfile, tarfile, shutil | |
sqlite3 pkg | import sqlite3 | in the stdlib |
Intl | locale / babel | |
big-integer pkg | (built in) | ints are arbitrary precision |
date-fns | datetime + zoneinfo |
Library Equivalents (npm → PyPI)
The package you'd reach for in Node, and what to install instead.
| JavaScript | Python | Notes |
|---|---|---|
express / fastify | FastAPI / Flask / Django | FastAPI for APIs, Django for full apps |
axios / node-fetch | requests / httpx | |
zod / yup / joi | pydantic | |
prisma / typeorm / knex | SQLAlchemy | Alembic for migrations |
mongoose | pymongo / beanie | |
jest / vitest | pytest | |
eslint + prettier | ruff | one tool, both jobs |
typescript | mypy / pyright | |
lodash | itertools + functools + collections | mostly stdlib |
dayjs / date-fns | datetime / pendulum / arrow | |
dotenv | python-dotenv | |
winston / pino | logging / structlog / loguru | |
bullmq / agenda | celery / rq / dramatiq | |
socket.io | websockets / channels | |
puppeteer | playwright / selenium | |
cheerio | beautifulsoup4 / lxml | |
sharp | Pillow | |
d3 / chart.js | matplotlib / plotly / seaborn | |
danfo.js | pandas | |
tensorflow.js | PyTorch / TensorFlow | |
langchain.js | langchain / llamaindex | |
commander / yargs | typer / click / argparse | |
nodemon | watchfiles / uvicorn --reload | |
pm2 | gunicorn / supervisor / systemd |
Data & ML Environment
The reason most JavaScript developers pick up Python in the first place.
| JavaScript | Python | Notes |
|---|---|---|
npm / nvm | uv | one tool for interpreters, deps and running |
(no equivalent) | conda / miniforge | when you need CUDA and native binaries |
Observable notebooks | JupyterLab | run code cell by cell |
typed arrays | NumPy | vectorised math, no Python-level loops |
danfo.js / arquero | pandas | DataFrames — the workhorse |
(no equivalent) | polars | faster, Rust-backed DataFrames |
chart.js / d3 | matplotlib / seaborn / plotly | |
ml5.js / brain.js | scikit-learn | classic ML |
tensorflow.js | PyTorch | the research + production default |
transformers.js | transformers (Hugging Face) | |
langchain.js | langchain / llama-index | |
openai (node) | openai / anthropic | official Python SDKs |
vector DB clients | chromadb / qdrant-client / pgvector | |
express API for a model | FastAPI + pydantic | auto-generated OpenAPI docs |
(no equivalent) | streamlit / gradio | instant data apps and demos |
Python-Only Features (no clean JS equivalent)
Things worth learning because there's simply nothing like them in JavaScript.
| JavaScript | Python | Notes |
|---|---|---|
(no equivalent) | x := f() | walrus operator — assign inside an expression |
(no equivalent) | match / case | structural pattern matching + destructuring |
(no equivalent) | [f(x) for x in a if g(x)] | comprehensions for list/dict/set/generator |
(no equivalent) | @decorator | first-class function/class wrapping syntax |
(no equivalent) | with open(p) as f: | deterministic setup/teardown |
(no equivalent) | a[1:9:2] | slice syntax with a step, on any sequence |
(no equivalent) | 1 < x < 10 | chained comparisons |
(no equivalent) | def f(*, key=1) | keyword-only arguments |
(no equivalent) | @dataclass | auto __init__/__repr__/__eq__ from fields |
(no equivalent) | __slots__ | fixed attribute set, lower memory |
(no equivalent) | operator overloading | __add__, __eq__, __lt__ on your own classes |
(no equivalent) | for ... else | else runs when the loop wasn't broken |
(no equivalent) | a, *rest, b = items | starred unpacking anywhere in the pattern |
(no equivalent) | multiple return values | return a, b — it's just a tuple |
(no equivalent) | 2 ** 1000 | arbitrary-precision integers by default |
(no equivalent) | help(obj) / dir(obj) | runtime introspection in the REPL |
(no equivalent) | docstrings | documentation as a first-class language feature |
Gotchas That Bite JavaScript Developers
The bugs you will write in your first week. Read this one twice.
| JavaScript | Python | Notes |
|---|---|---|
[] is truthy | [] is falsy | if arr: means 'if not empty' |
NaN is falsy | float('nan') is truthy | use math.isnan() |
default params fresh each call | def f(a=[]) ← shared! | use None and build inside |
a = b copies primitives | a = b aliases the SAME list | use a.copy() / deepcopy |
'5' + 3 works | TypeError | convert explicitly |
arr[99] → undefined | IndexError | check bounds or use .get on dicts |
obj.missing → undefined | KeyError | use .get(key, default) |
s.replace replaces first | s.replace replaces ALL | pass a count for the JS behaviour |
indexOf returns -1 | index() raises ValueError | use .find() on strings |
i++ works | SyntaxError | use i += 1 |
mixed tabs/spaces fine | IndentationError / TabError | spaces only, 4 per level |
a.sort() is lexicographic | a.sort() is numeric | JS is the odd one here |
truthy string concat in print | print(1 + '1') → TypeError | print(1, '1') instead |
{} is an empty object | {} is an empty DICT | set() for an empty set |
(1) is just 1 | (1) is just 1, (1,) is a tuple | trailing comma matters |
closures capture per-iteration (let) | closures capture the variable | use a default arg to snapshot |
private #fields | self.__x is only mangled | nothing is truly private |
import is hoisted | import runs top to bottom | circular imports fail loudly |
async fn runs on call | coroutine does nothing until awaited | 'coroutine was never awaited' warning |
ms everywhere | seconds everywhere | time.sleep(1) is one second |
JavaScript to Python — frequently asked questions
Is Python hard to learn if I already know JavaScript?
No — the concepts transfer almost one-to-one. Variables, functions, loops, closures, classes, async/await and modules all exist in both. What changes is the syntax (indentation instead of braces, and/or/not instead of &&/||/!) and the strictness: Python refuses to coerce types, so you convert explicitly. Most JavaScript developers are productive in Python within a few days and comfortable within two weeks.
What is the Python equivalent of console.log?
print(). It accepts multiple arguments — print(a, b, c) — and separates them with spaces. For structured output use pprint.pp(obj), and for real application logging use the logging module instead of print.
What is the Python equivalent of map, filter and reduce?
map and filter exist as builtins but return lazy iterators, so you usually wrap them in list(). The idiomatic Python is a list comprehension: [f(x) for x in items] for map, and [x for x in items if g(x)] for filter. reduce moved into functools — from functools import reduce — but for summing you should just use the sum() builtin.
Does Python have === strict equality?
No, and it doesn't need one. Python's == already compares by value without type coercion, so 1 == '1' is False. The `is` operator compares identity (the same object in memory) and should be reserved for None, True, False and sentinel objects.
What replaces npm and package.json in Python?
pyproject.toml replaces package.json, and uv is the modern replacement for npm + nvm + npx in a single binary: uv init, uv add requests, uv run main.py, uv python install 3.12. pip and virtualenv are the older, still-common combination, and conda/miniforge is preferred for machine-learning stacks with native or CUDA dependencies.
Is async/await the same in Python and JavaScript?
The keywords are identical but the execution model differs in one important way. In JavaScript, calling an async function starts running it immediately. In Python, calling an async def function only creates a coroutine object — nothing happens until you await it or schedule it on an event loop with asyncio.run(), asyncio.create_task() or asyncio.gather(). Forgetting this produces the familiar 'coroutine was never awaited' warning.
Why is an empty array truthy in JavaScript but falsy in Python?
Python defines truthiness for containers by their length: empty list, dict, set, tuple and string are all falsy. That's why the idiomatic emptiness check is `if not items:` rather than `if len(items) == 0:`. JavaScript, by contrast, treats every object — including [] and {} — as truthy.
What is the Python equivalent of the spread operator?
For sequences, use * — [*a, *b] to merge lists and f(*args) to spread positional arguments. For dictionaries and keyword arguments, use ** — {**a, **b} to merge dicts (or a | b in Python 3.9+) and f(**opts) to spread keyword arguments.
Does Python have object destructuring like const {a, b} = obj?
Not for dictionaries. You can unpack sequences — a, b = pair and a, *rest = items — and pattern-match with match/case, but there is no dict destructuring syntax. The usual approach is a, b = obj["a"], obj["b"], or defining a function with keyword-only parameters and calling it with f(**obj).
Should I learn Python or stick with JavaScript?
Learn both — they cover different ground. JavaScript owns the browser and much of full-stack web development; Python owns data, machine learning, AI/LLM tooling, scripting and scientific computing. If you're moving into AI engineering, data work or automation, Python is effectively required, and coming from JavaScript you already have every concept you need.