60 mappings
Rosetta cheatsheet
From the JavaScript you already write to Python 3.14+. Anything tagged footgun looks equivalent but behaves differently.
60 of 60
Transform every element
TypeScriptxs.map(x => x * 2)
Python 3.14+[x * 2 for x in xs]
Keep elements matching a predicate
TypeScriptxs.filter(x => x > 2)
Python 3.14+[x for x in xs if x > 2]
Filter then transform in one pass
TypeScriptxs.filter(x => x > 2).map(x => x * 2)
Python 3.14+[x * 2 for x in xs if x > 2]
Fold to a single value
TypeScriptxs.reduce((a, b) => a + b, 0)
Python 3.14+sum(xs) # or functools.reduce(op.add, xs, 0)
First element matching a predicate
TypeScriptxs.find(x => x > 2)
Python 3.14+next((x for x in xs if x > 2), None)
Any / all match
TypeScriptxs.some(f) / xs.every(f)
Python 3.14+any(f(x) for x in xs) / all(f(x) for x in xs)
Membership test
TypeScriptxs.includes(3)
Python 3.14+3 in xs
Last element
footgunTypeScriptxs.at(-1)
Python 3.14+xs[-1]
JS
xs[-1] is a property lookup returning undefined; Python indexes from the end and raises IndexError when empty.Sub-sequence
TypeScriptxs.slice(1, 3)
Python 3.14+xs[1:3]
Reversed copy
footgunTypeScript[...xs].reverse()
Python 3.14+xs[::-1]
JS
.reverse() mutates in place; the spread is what makes it a copy.Sort by a key
footgunTypeScript[...xs].sort((a, b) => a.age - b.age)
Python 3.14+sorted(xs, key=lambda x: x.age)
Python
xs.sort() mutates and returns None; sorted() returns a new list. JS .sort() does both at once, and sorts lexicographically without a comparator.Concatenate
TypeScript[...a, ...b]
Python 3.14+[*a, *b] # or a + b
Head and tail
TypeScriptconst [first, ...rest] = xs
Python 3.14+first, *rest = xs
Index with each element
TypeScriptxs.forEach((x, i) => ...)
Python 3.14+for i, x in enumerate(xs): ...
Iterate two sequences together
footgunTypeScripta.map((x, i) => [x, b[i]])
Python 3.14+list(zip(a, b))
zip stops at the shorter input; pass strict=True (3.10+) to raise instead.Flatten one level
TypeScriptxs.flat()
Python 3.14+[y for x in xs for y in x]
Deduplicate
footgunTypeScript[...new Set(xs)]
Python 3.14+list(dict.fromkeys(xs)) # order-preserving
Python
set(xs) deduplicates but loses order; dict.fromkeys keeps it.Numeric range
TypeScriptArray.from({ length: 5 }, (_, i) => i)
Python 3.14+list(range(5))
Read with a fallback
footgunTypeScriptobj.key ?? "default"
Python 3.14+d.get("key", "default")
d["key"] raises KeyError; there is no silent undefined.Merge / override
TypeScript{ ...a, k: v }
Python 3.14+{**a, "k": v} # or a | {"k": v} (3.9+)
Iterate key/value pairs
TypeScriptObject.entries(obj)
Python 3.14+d.items()
Keys / values
TypeScriptObject.keys(o) / Object.values(o)
Python 3.14+d.keys() / d.values()
Build a mapping from a list
TypeScriptObject.fromEntries(xs.map(x => [x.id, x]))
Python 3.14+{x.id: x for x in xs}
Key exists
TypeScript"k" in obj
Python 3.14+"k" in d
Remove a key
TypeScriptdelete obj.k
Python 3.14+del d["k"] # or d.pop("k", None)
Set algebra
TypeScriptno built-in, filter over a Set
Python 3.14+a | b, a & b, a - b, a ^ b
Safe nested access
footgunTypeScripta?.b?.c
Python 3.14+a.get("b", {}).get("c") # or try/except
Python has no
?.. The idiom is EAFP. Attempt the access and catch it, rather than testing first.Typed record
TypeScriptinterface User { id: string }
Python 3.14+@dataclassclass User:id: str
Anonymous function
footgunTypeScript(a, b) => a + b
Python 3.14+lambda a, b: a + b
A lambda body is a single expression. No statements, no multi-line blocks.
Default parameter
footgunTypeScriptfunction f(xs = []) {}
Python 3.14+def f(xs=None):xs = [] if xs is None else xs
Python evaluates defaults once at definition time, so a mutable default is shared across every call. JS evaluates them per call.
Variadic arguments
TypeScriptfunction f(...args) {}
Python 3.14+def f(*args, **kwargs): ...
Force named arguments
TypeScriptfunction f({ apiKey, timeout = 30 }) {}
Python 3.14+def f(*, api_key: str, timeout: int = 30): ...
Write to an enclosing variable
footgunTypeScriptlet n = 0; const inc = () => { n += 1 }
Python 3.14+n = 0def inc():nonlocal n # or global at module leveln += 1
Assigning to a name makes it local for the whole function unless declared
nonlocal/global. Reading alone needs no declaration.Lazy sequence
TypeScriptfunction* gen() { yield 1 }
Python 3.14+def gen():yield 1
Bind leading arguments
TypeScriptf.bind(null, 1)
Python 3.14+functools.partial(f, 1)
Wrap a function
TypeScriptconst wrapped = withLog(f)
Python 3.14+@with_logdef f(): ...
Start the async world
footgunTypeScriptawait main() // top-level await
Python 3.14+asyncio.run(main())
Node keeps an event loop running always. Python has no loop until you start one. An un-awaited coroutine simply never executes.
Run concurrently, fail fast
TypeScriptawait Promise.all([p1, p2])
Python 3.14+await asyncio.gather(t1, t2)
Run concurrently, collect errors
TypeScriptawait Promise.allSettled([p1, p2])
Python 3.14+await asyncio.gather(t1, t2, return_exceptions=True)
First to settle wins
TypeScriptawait Promise.race([p1, p2])
Python 3.14+await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
Start work without awaiting it yet
footgunTypeScriptconst p = fetchUser() // already running
Python 3.14+task = asyncio.create_task(fetch_user())
A Promise starts on creation; a coroutine object does nothing until awaited or wrapped in a task. Calling an
async def and dropping the result is a silent no-op.Run tasks that all share one lifetime, cancel siblings on failure
footgunTypeScriptawait Promise.all([p1, p2]) // siblings keep running on rejection
Python 3.14+async with asyncio.TaskGroup() as tg:tg.create_task(coro1())tg.create_task(coro2())# except* ExceptionGroup to catch failures
A TaskGroup failure raises an ExceptionGroup caught only by
except*, and no task survives the block, unlike Promise.all where abandoned siblings finish in the background.Delay
TypeScriptawait new Promise(r => setTimeout(r, 1000))
Python 3.14+await asyncio.sleep(1)
Abort slow work
TypeScriptAbortSignal.timeout(5000)
Python 3.14+async with asyncio.timeout(5): ...
Limit concurrency
TypeScriptp-limit / a manual queue
Python 3.14+sem = asyncio.Semaphore(3)async with sem: ...
Iterate an async stream
TypeScriptfor await (const x of stream)
Python 3.14+async for x in stream:
Catch a specific failure
footgunTypeScripttry { ... } catch (e) { if (!(e instanceof T)) throw e }
Python 3.14+try:...except ValueError as e:...
JS catches everything and re-throws; Python filters by exception type in the clause.
Always run / run only on success
TypeScripttry { ... } finally { ... }
Python 3.14+try:...except E:...else:# only if no exceptionfinally:...
Raise
TypeScriptthrow new Error("boom")
Python 3.14+raise ValueError("boom")
Wrap and preserve the cause
TypeScriptthrow new Error("ctx", { cause: e })
Python 3.14+raise RuntimeError("ctx") from e
Domain-specific error type
TypeScriptclass NotFound extends Error {}
Python 3.14+class NotFound(Exception): ...
Guard vs attempt
footgunTypeScriptif (obj?.key) use(obj.key) // LBYL
Python 3.14+try:use(d["key"])except KeyError:... # EAFP
Idiomatic Python attempts and catches rather than checking first.
try costs nothing when no exception is raised.Read a whole text file
TypeScriptawait readFile("a.txt", "utf8")
Python 3.14+Path("a.txt").read_text()
Write a text file
TypeScriptawait writeFile("a.txt", data)
Python 3.14+Path("a.txt").write_text(data)
Scoped handle
TypeScriptconst fh = await open(p); try { ... } finally { await fh.close() }
Python 3.14+with open(p) as fh:...
Stream lines without loading the file
TypeScriptcreateInterface({ input: createReadStream(p) })
Python 3.14+with open(p) as fh:for line in fh: ...
Parse / serialise JSON
footgunTypeScriptJSON.parse(s) / JSON.stringify(o)
Python 3.14+json.loads(s) / json.dumps(o)
json.dumps writes Python None/True as JSON null/true, but tuples become arrays and dict keys are coerced to strings.Build a path
TypeScriptpath.join(a, b)
Python 3.14+Path(a) / b
Environment variable with a default
TypeScriptprocess.env.KEY ?? "x"
Python 3.14+os.environ.get("KEY", "x")
Find files by pattern
TypeScriptawait glob("**/*.ts")
Python 3.14+Path(".").rglob("*.py")