JavaScript → Python cheatsheet
690 side-by-side equivalents across 35 groups

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.

The golden rule: JavaScript is permissive and coerces types aggressively. Python is strict and refuses to guess — you convert explicitly. Indentation is real syntax; there are no braces.

Basics & Mental Model

The first hour of writing Python as a JavaScript developer: printing, comments, blocks and running a file.

JavaScriptPythonNotes
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
{ ... } blocksindentation (4 spaces)indentation IS syntax — no braces anywhere
statement;statementno semicolons; newline ends a statement
line continuation with ( )\ at end of line, or wrap in ( )parentheses are the idiomatic way
node app.jspython app.pypython3 app.py on older macOS/Linux setups
node (REPL)python (REPL)or ipython for a much nicer REPL
npx tsx watch app.tsuv run --watch app.pyor watchfiles / nodemon-style tools
camelCase namessnake_case namesPEP 8: functions/vars snake_case, classes PascalCase
const MAX = 10MAX = 10UPPER_CASE is a convention only — nothing is enforced
'use strict'(always strict)Python has no sloppy mode
debuggerbreakpoint()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.

JavaScriptPythonNotes
let x = 0x = 0no keyword, no semicolon
const y = 1y = 1no real constants; UPPER_CASE signals intent
var z = 2z = 2Python scoping is function-level, no hoisting of values
let a, b; a = b = 1a = b = 1
[a, b] = [b, a]a, b = b, atuple swap — no temp variable
null / undefinedNonePython has exactly one 'nothing' value
true / falseTrue / Falsecapitalised
"text" / 'text'"text" / 'text'both quote styles identical
`multi\nline`"""multi line"""triple quotes preserve newlines
42, 3.1442, 3.14int has UNLIMITED precision — 2 ** 1000 just works
9007199254740993n (BigInt)9007199254740993every Python int is a bigint
0.1 + 0.2 // 0.300000000000000040.1 + 0.2 # same float painuse decimal.Decimal for money
typeof xtype(x)returns the class: <class 'int'>
typeof x === "string"isinstance(x, str)isinstance is the idiomatic check
x === undefinedx is Noneis compares identity, == compares value
x === nullx is None
Number.MAX_SAFE_INTEGER(no limit for int)sys.float_info.max for floats
NaNfloat("nan")math.nan; check with math.isnan(x)
Infinityfloat("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.

JavaScriptPythonNotes
"5" + 3 // "53""5" + str(3)mixing str and int raises TypeError
"5" - 3 // 2int("5") - 3no 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.

JavaScriptPythonNotes
0 → falsy0 → falsysame
"" → falsy"" → falsysame
null / undefined → falsyNone → falsysame
NaN → FALSYfloat("nan") → TRUTHYclassic gotcha — use math.isnan()
[] → TRUTHY[] → FALSYempty list is falsy in Python
{} → TRUTHY{} → FALSYempty dict/set/tuple are all falsy
arr.length === 0not arridiomatic emptiness check
arr.length > 0if arr:
=== / !==== / !=no === ; == already compares by value + type
Object.is(a, b)a is bidentity, not value
a == b (loose)(no equivalent)Python has no loose equality — good riddance
[1,2] === [1,2] // false[1,2] == [1,2] # TruePython compares contents
JSON.stringify(a)===JSON.stringify(b)a == bdeep equality is built in
a === b (reference)a is buse `is` only for None/True/False/sentinels
1 < 2 && 2 < 31 < 2 < 3Python chains comparisons

Operators

Words instead of symbols for logic; a real integer-division and power operator.

JavaScriptPythonNotes
&& / || / !and / or / notkeywords, not symbols
a ? b : cb if a else cvalue first, condition in the middle
x ?? yy if x is None else xno nullish operator
x || y (default)x or ysame falsy-based fallback
x ||= yx = x or yno 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 ** bsame operator
Math.floor(a / b)a // bfloor division (careful with negatives)
a % ba % bPython's % follows the sign of the divisor: -7 % 3 == 2
x++ / x--x += 1 / x -= 1no increment operators
a & b, a | b, a ^ b, ~a, <<, >>samebitwise 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 Bisinstance(a, B)
'k' in obj"k" in objdict membership checks keys
void 0None
(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.

JavaScriptPythonNotes
s.lengthlen(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') / endsWiths.startswith("a") / s.endswith("a")accepts a tuple of options
s.replace('a','b') // first onlys.replace("a", "b") # ALLs.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.

JavaScriptPythonNotes
`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 Templatesafe user-supplied templates
date.toISOString()dt.isoformat()
Intl.DateTimeFormatdt.strftime("%Y-%m-%d")

Numbers & Math

Most Math.* helpers are builtins or live in the math module.

JavaScriptPythonNotes
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) / minmax(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.Emath.pi / math.e
Math.log(x) / log2 / log10math.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 Decimalexact decimal arithmetic for money
(no equivalent)from fractions import Fractionrational numbers
(no equivalent)import statisticsmean, median, stdev in the stdlib

Arrays → Lists

Lists are mutable sequences. Slicing replaces half the array API.

JavaScriptPythonNotes
const a = [1,2,3]a = [1, 2, 3]
new Array(3).fill(0)[0] * 3careful: [[]]*3 shares the same inner list
Array.from({length:n},(_,i)=>i)list(range(n))
a.lengthlen(a)
a[10] // undefineda[10] # IndexErrorPython 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 = 0a.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 / manuala.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.

JavaScriptPythonNotes
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 manuallyfor 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 streamitertools.islice(gen, 5)works on any iterator
array.reverse() in loopfor 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.

JavaScriptPythonNotes
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 fieldsa.sort(key=lambda p: (p.last, p.first))tuple key
sort desc by one fieldsorted(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, attrgetterfaster 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 manuallybisect.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.

JavaScriptPythonNotes
const o = {a: 1}o = {"a": 1}string keys must be quoted
{ [key]: v }{key: v}keys are ordinary expressions
o.ao["a"]no dot access on dicts
o.missing // undefinedo["missing"] # KeyError
o.x ?? defo.get("x", def).get() returns None by default
delete o.adel o["a"]o.pop("a", None) to delete safely
'a' in o"a" in ochecks 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).lengthlen(o)
Object.fromEntries(pairs)dict(pairs)
Object.entries(o).map(...){k: f(v) for k, v in o.items()}dict comprehension
const {a, b} = oa, 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)
Mapdictdict preserves insertion order (3.7+)
WeakMapweakref.WeakKeyDictionary
nested optional chaining o?.a?.bo.get("a", {}).get("b")no ?. operator

Sets, Tuples & Immutability

Python gives you first-class set algebra and a real immutable sequence.

JavaScriptPythonNotes
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.sizelen(s)
[...new Set(a)]list(set(a))dedupe — order not preserved
dedupe keeping orderlist(dict.fromkeys(a))classic trick
union (manual)a | b # a.union(b)
intersection (manual)a & b
difference (manual)a - b
symmetric differencea ^ b
subset check (manual)a <= ba < 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] = paira, b = pairtuple unpacking
const [a, ...rest] = arra, *rest = arrstarred 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 namedtuplelightweight record type
(no equivalent)from typing import NamedTupletyped, class-style namedtuple

Control Flow

elif, match/case, and for-else — plus no do/while.

JavaScriptPythonNotes
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: breakno 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 / continuebreak / continuesame
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 { }passpass 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.

JavaScriptPythonNotes
function f(a, b) { }def f(a, b):
const f = (x) => x * xf = lambda x: x * xor just def f(x): return x * x
const f = x => { ...; return y }def f(x): ...lambdas hold ONE expression only
function f() { }def f(): passreturns None implicitly
returnreturnbare 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*argsno implicit arguments
fn.lengthlen(inspect.signature(f).parameters)
f.call(obj) / apply / bindf(obj) / functools.partial(f, obj)no `this` to rebind
const g = f.bind(null, 1)g = functools.partial(f, 1)
higher-order wrap(fn)@decoratordecorator syntax
memoize(fn)@functools.cacheor @lru_cache(maxsize=128)
typeof f === 'function'callable(f)
IIFE (() => {})()(lambda: ...)()rarely used — just call a def
function* gen() { yield 1 }def gen(): yield 1generators
gen().next().valuenext(g)raises StopIteration when exhausted
yield*yield from

Scope, Closures & `this`

No hoisting drama, no `this` binding — but you must declare intent to rebind.

JavaScriptPythonNotes
block scoping with letfunction scopingloops/ifs do NOT create a scope
closures capture variablesclosures capture variablessame idea
outer = 1 inside a fnnonlocal outerneeded to REASSIGN an enclosing var
global var assignmentglobal xneeded 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 ReferenceErrorUnboundLocalErrorassigning later in a fn shadows the global
module scope is isolatedmodule scope is isolatedeach .py file is a module
globalThisglobals()locals() too
setTimeout closure-in-loop bugsame late-binding bugfix with a default arg: lambda x=x: ...

Comprehensions & Generators

The single most Pythonic construct — worth learning on day one.

JavaScriptPythonNotes
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 streamsum(x*x for x in a)no intermediate list allocated
function* g(){}def g(): yield xgenerator 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.

JavaScriptPythonNotes
class Book { }class Book:
constructor(t) { }def __init__(self, t):__new__ is the actual allocator
this.title = tself.title = tself 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 fieldself.__xname mangling, not true privacy
_conventionself._xsingle underscore = 'internal, please don't touch'
toString()__str__ / __repr__repr is for developers, str for users
Symbol.iterator__iter__
valueOf()__int__ / __float__
instanceofisinstance(obj, C)
obj.constructor.nametype(obj).__name__
Object.getPrototypeOfC.__mro__method resolution order
abstract class (TS)from abc import ABC, abstractmethod
interface (TS)typing.Protocolstructural typing
plain data class@dataclassauto __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.

JavaScriptPythonNotes
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 encoderjson.JSONEncoder subclass
Symbol.toPrimitive__bool__ / __int__ / __float__

Modules, Imports & Project Layout

Dots instead of slashes, and __init__.py instead of index.js.

JavaScriptPythonNotes
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__.pymakes a folder a package
"./sibling"from .sibling import xleading dot = relative import
"../parent"from ..parent import xtwo dots = one level up
require() dynamicimportlib.import_module(name)
top-level await in ESMasyncio.run(main())
package.jsonpyproject.tomlPEP 621 metadata
node_modules.venvvirtual environment per project
package-lock.jsonuv.lock / poetry.lock / requirements.txt
.nvmrc.python-version
NODE_PATHPYTHONPATH
circular import warningImportError on circular importsmove the import inside the function
module cachingsys.modules cachesame behaviour

Tooling: npm → uv / pip

uv is the modern single tool — think npm + nvm + npx in one binary.

JavaScriptPythonNotes
npm inituv initcreates pyproject.toml
npm installuv syncinstalls from the lockfile
npm install pkguv add pkgpip install pkg for the classic way
npm install -D pkguv add --dev pkg
npm uninstall pkguv remove pkg
npm run scriptuv run scriptscripts live in pyproject.toml
npx tooluvx toolrun without installing
nvm install 20uv python install 3.12uv manages interpreters too
npm lsuv pip list
npm outdateduv lock --upgrade
yarn / pnpmpoetry / pipenv / pdmolder alternatives to uv
node_modules/.bin.venv/binactivate with source .venv/bin/activate
eslintruffruff check — extremely fast linter
prettierruff format / black
typescript / tscmypy / pyrightstatic type checkers
jest / vitestpytest
huskypre-commitgit hook manager
npm publishuv build && uv publishto PyPI
semantic-releasehatch / bump-my-version
(no equivalent)conda / miniforgefor ML stacks with native/CUDA binaries

Errors & Exceptions

except instead of catch — and an else clause you'll actually use.

JavaScriptPythonNotes
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)raisebare 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.messagestr(e)e.args holds the raw arguments
error.stacktraceback.format_exc()import traceback
error.causeraise New() from errexplicit exception chaining
(no equivalent)try/except/else:else runs only when nothing raised
catch and ignoreexcept SomeError: passnever bare `except:`
Promise.allSettled errorsasyncio.gather(..., return_exceptions=True)
process.on('uncaughtException')sys.excepthook
AggregateErrorExceptionGroup / except*Python 3.11+
custom error codesraise ValueError / KeyError / TypeErroruse the built-in hierarchy
undefined is not a functionAttributeError / TypeErrorthe equivalent runtime blow-up
arr[99] → undefinedIndexErrorPython fails loudly
obj.missing → undefinedKeyError / AttributeError

Async & Concurrency

Same async/await keywords — but coroutines don't start until you run them on a loop.

JavaScriptPythonNotes
async function f() { }async def f():
await pawait ponly valid inside async def
f() starts immediatelyf() returns a coroutinenothing runs until awaited/scheduled
f().then(...)asyncio.run(f())the entry point for an async program
PromiseAwaitable / 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 awaitasyncio.create_task(coro())fire-and-forget — keep a reference
Promise.all([a, b])await asyncio.gather(a, b)returns results in order
Promise.allSettledasyncio.gather(*t, return_exceptions=True)
Promise.raceasyncio.wait(t, return_when=asyncio.FIRST_COMPLETED)
Promise.any(compose with asyncio.wait)
AbortControllertask.cancel()raises CancelledError inside the coroutine
timeout wrapperasync with asyncio.timeout(5):Python 3.11+
for await (const x of s)async for x in s:async iteration
(no equivalent)async withasync context managers
worker_threadsthreading / concurrent.futuresGIL limits CPU-bound threads
cluster / child_processmultiprocessingtrue parallelism for CPU work
p-limitasyncio.Semaphore(n)bound concurrency
queueMicrotaskloop.call_soon
event loopasyncio event loopsame 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.

JavaScriptPythonNotes
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.yearplain 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 formatd.strftime("%Y-%m-%d %H:%M")
parse custom formatdatetime.strptime(s, "%d/%m/%Y")
Intl / moment-timezonefrom zoneinfo import ZoneInfoZoneInfo("Europe/Berlin")
sleep via setTimeouttime.sleep(1)blocking; await asyncio.sleep in async code
cron (node-cron)APScheduler / cron
(no equivalent)datetime.date / datetime.timedate-only and time-only types

Regular Expressions

No literal syntax — patterns are raw strings passed to the `re` module.

JavaScriptPythonNotes
/abc/r"abc"the r prefix stops backslash escaping
/abc/ire.IGNORECASE flagre.search(p, s, re.I)
re.test(s)re.search(p, s) is not Nonesearch 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 \sUnicode-aware by default in Python 3

JSON, Files & Paths

`with` blocks replace manual close(); pathlib replaces the path module.

JavaScriptPythonNotes
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 + linesfor line in open(p):lazy, memory-friendly
manual close()with open(p) as fh:context manager closes automatically
fs.appendFileSyncopen(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.copyFileSyncshutil.copy(src, dst)
path.join(a, b)Path(a) / bthe / 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()
__dirnamePath(__file__).parent
__filename__file__
os.tmpdir()tempfile.gettempdir()tempfile.NamedTemporaryFile
csv-parseimport csvcsv.DictReader in the stdlib
yamlimport yaml # PyYAMLthird-party but ubiquitous

HTTP, APIs & Servers

requests for sync, httpx/aiohttp for async, FastAPI for serving.

JavaScriptPythonNotes
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.statusres.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 instancerequests.Session() / httpx.Client()connection pooling
URLSearchParamsparams={...}or urllib.parse.urlencode
new URL(u)urllib.parse.urlparse(u)
encodeURIComponent(s)urllib.parse.quote(s)
express / fastifyFastAPIasync, typed, auto OpenAPI docs
app.get('/x', handler)@app.get("/x") async def handler():decorator routing
zod validationpydanticFastAPI uses it for request models
nodemon / ts-node-devuvicorn main:app --reload
ws / socket.iowebsockets / FastAPI WebSocket
node-fetch retry libstenacityretry decorators
cheeriobeautifulsoup4HTML parsing
puppeteer / playwrightplaywright (python)same API, Python bindings

Testing & Debugging

pytest with plain assert — no expect().toBe() chains needed.

JavaScriptPythonNotes
describe / itdef test_thing():any function named test_*
expect(a).toBe(b)assert a == bpytest rewrites asserts for good output
expect(a).toEqual(obj)assert a == objdeep equality is native
expect(fn).toThrow()with pytest.raises(ValueError):
beforeEach@pytest.fixturefixtures are injected by parameter name
test.each([...])@pytest.mark.parametrize(...)
jest.mock()unittest.mock / monkeypatchpytest's monkeypatch fixture
jest.spyOnmocker.patch (pytest-mock)
test.skip / only@pytest.mark.skip / -k filter
npm testpytestpytest -q for quiet output
--coveragepytest --covpytest-cov
console.log debuggingprint() / breakpoint()
node --inspectpython -m pdb app.py
winston / pinoimport logginglogging.getLogger(__name__)
console.timetime.perf_counter()or timeit / cProfile
clinic / 0x profilercProfile + snakevizpy-spy for live processes

Type Hints (TypeScript → Python)

Optional, gradual, checked by mypy/pyright — never enforced at runtime.

JavaScriptPythonNotes
let x: numberx: int = 0annotations are optional everywhere
function f(a: string): numberdef 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 | BA | BUnion[A, B] on older versions
T | nullT | NoneOptional[T]
anyAnyfrom typing import Any
unknownobject
neverNoReturn
interface User { }class User(TypedDict):or a Protocol / dataclass
type aliastype UserId = intPython 3.12 syntax; else UserId = int
generics <T>def f[T](x: T) -> T:3.12 syntax; else TypeVar
as const / literal typesLiteral["a", "b"]
readonlyFinalfrom typing import Final
enumfrom enum import Enum
zod runtime validationpydantic BaseModelvalidation actually enforced at runtime
tsc --noEmitmypy . / pyright

Environment, CLI & Process

os and sys replace the process global; argparse replaces yargs.

JavaScriptPythonNotes
process.env.KEYos.environ["KEY"]import os
process.env.KEY ?? 'x'os.getenv("KEY", "x")
dotenvfrom dotenv import load_dotenvpython-dotenv
process.argvsys.argvsame shape, argv[0] is the script
yargs / commanderargparsein the stdlib; typer/click are nicer
process.cwd()os.getcwd()or Path.cwd()
process.exit(0)sys.exit(0)
process.platformsys.platform
process.versionsys.version
child_process.execSyncsubprocess.run([...], capture_output=True)pass a list, not a string
process.on('SIGINT')signal.signal(signal.SIGINT, handler)
os.cpus().lengthos.cpu_count()
readline questioninput('Name: ')builtin
chalkrich / coloramarich also does tables, progress bars, markdown
ora spinnerrich.progress / tqdm

Standard Library (batteries included)

Much of what you'd npm install in Node already ships with Python.

JavaScriptPythonNotes
lodash.countBycollections.Counter(a).most_common(3) for top-N
lodash.groupBycollections.defaultdict(list)or itertools.groupby on sorted data
deque / linked list pkgcollections.dequeO(1) appendleft/popleft
lodash.chunkitertools.batched(a, n)Python 3.12+
lodash.uniqlist(dict.fromkeys(a))order-preserving dedupe
lodash.zipzip(a, b)builtin
lodash.rangerange(n)builtin
lodash.partialfunctools.partial
lodash.memoizefunctools.cache
ramda pipe/compose(no builtin)chain comprehensions or use functools.reduce
uuidimport uuid; uuid.uuid4()
crypto.createHashhashlib.sha256(b).hexdigest()
crypto.randomBytessecrets.token_hex(16)use secrets, not random, for tokens
base64 helpersimport base64
querystringurllib.parse
zlib / targzip, zipfile, tarfile, shutil
sqlite3 pkgimport sqlite3in the stdlib
Intllocale / babel
big-integer pkg(built in)ints are arbitrary precision
date-fnsdatetime + zoneinfo

Library Equivalents (npm → PyPI)

The package you'd reach for in Node, and what to install instead.

JavaScriptPythonNotes
express / fastifyFastAPI / Flask / DjangoFastAPI for APIs, Django for full apps
axios / node-fetchrequests / httpx
zod / yup / joipydantic
prisma / typeorm / knexSQLAlchemyAlembic for migrations
mongoosepymongo / beanie
jest / vitestpytest
eslint + prettierruffone tool, both jobs
typescriptmypy / pyright
lodashitertools + functools + collectionsmostly stdlib
dayjs / date-fnsdatetime / pendulum / arrow
dotenvpython-dotenv
winston / pinologging / structlog / loguru
bullmq / agendacelery / rq / dramatiq
socket.iowebsockets / channels
puppeteerplaywright / selenium
cheeriobeautifulsoup4 / lxml
sharpPillow
d3 / chart.jsmatplotlib / plotly / seaborn
danfo.jspandas
tensorflow.jsPyTorch / TensorFlow
langchain.jslangchain / llamaindex
commander / yargstyper / click / argparse
nodemonwatchfiles / uvicorn --reload
pm2gunicorn / supervisor / systemd

Data & ML Environment

The reason most JavaScript developers pick up Python in the first place.

JavaScriptPythonNotes
npm / nvmuvone tool for interpreters, deps and running
(no equivalent)conda / miniforgewhen you need CUDA and native binaries
Observable notebooksJupyterLabrun code cell by cell
typed arraysNumPyvectorised math, no Python-level loops
danfo.js / arqueropandasDataFrames — the workhorse
(no equivalent)polarsfaster, Rust-backed DataFrames
chart.js / d3matplotlib / seaborn / plotly
ml5.js / brain.jsscikit-learnclassic ML
tensorflow.jsPyTorchthe research + production default
transformers.jstransformers (Hugging Face)
langchain.jslangchain / llama-index
openai (node)openai / anthropicofficial Python SDKs
vector DB clientschromadb / qdrant-client / pgvector
express API for a modelFastAPI + pydanticauto-generated OpenAPI docs
(no equivalent)streamlit / gradioinstant data apps and demos

Python-Only Features (no clean JS equivalent)

Things worth learning because there's simply nothing like them in JavaScript.

JavaScriptPythonNotes
(no equivalent)x := f()walrus operator — assign inside an expression
(no equivalent)match / casestructural pattern matching + destructuring
(no equivalent)[f(x) for x in a if g(x)]comprehensions for list/dict/set/generator
(no equivalent)@decoratorfirst-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 < 10chained comparisons
(no equivalent)def f(*, key=1)keyword-only arguments
(no equivalent)@dataclassauto __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 ... elseelse runs when the loop wasn't broken
(no equivalent)a, *rest, b = itemsstarred unpacking anywhere in the pattern
(no equivalent)multiple return valuesreturn a, b — it's just a tuple
(no equivalent)2 ** 1000arbitrary-precision integers by default
(no equivalent)help(obj) / dir(obj)runtime introspection in the REPL
(no equivalent)docstringsdocumentation as a first-class language feature

Gotchas That Bite JavaScript Developers

The bugs you will write in your first week. Read this one twice.

JavaScriptPythonNotes
[] is truthy[] is falsyif arr: means 'if not empty'
NaN is falsyfloat('nan') is truthyuse math.isnan()
default params fresh each calldef f(a=[]) ← shared!use None and build inside
a = b copies primitivesa = b aliases the SAME listuse a.copy() / deepcopy
'5' + 3 worksTypeErrorconvert explicitly
arr[99] → undefinedIndexErrorcheck bounds or use .get on dicts
obj.missing → undefinedKeyErroruse .get(key, default)
s.replace replaces firsts.replace replaces ALLpass a count for the JS behaviour
indexOf returns -1index() raises ValueErroruse .find() on strings
i++ worksSyntaxErroruse i += 1
mixed tabs/spaces fineIndentationError / TabErrorspaces only, 4 per level
a.sort() is lexicographica.sort() is numericJS is the odd one here
truthy string concat in printprint(1 + '1') → TypeErrorprint(1, '1') instead
{} is an empty object{} is an empty DICTset() for an empty set
(1) is just 1(1) is just 1, (1,) is a tupletrailing comma matters
closures capture per-iteration (let)closures capture the variableuse a default arg to snapshot
private #fieldsself.__x is only manglednothing is truly private
import is hoistedimport runs top to bottomcircular imports fail loudly
async fn runs on callcoroutine does nothing until awaited'coroutine was never awaited' warning
ms everywhereseconds everywheretime.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.