Cheat sheets
The file to keep open. Everything here appears elsewhere in the guide with explanation; this is the lookup version. Grouped so you can find things by what you are trying to do rather than by topic.
Table of contents
- 1. Complexity at a glance
- 2. Python quick reference
- 3. TypeScript and JavaScript quick reference
- 4. Side-by-side: the same thing in both languages
- 5. Algorithm templates
- 6. Data structure choice
- 7. The trap list
- 8. Interview phrasebook
1. Complexity at a glance
Growth and what fits in one second
| Class | n=1e3 | n=1e6 | Python (measured) | Node (measured) |
|---|---|---|---|---|
| O(1) | 1 | 1 | unbounded | unbounded |
| O(log n) | 10 | 20 | ~1e26 | effectively unbounded |
| O(sqrt n) | 32 | 1,000 | 5.6e13 | 4.0e15 |
| O(n) | 1e3 | 1e6 | 2.4e7 | 8.2e8 |
| O(n log n) | 1e4 | 2e7 | 3.5e5 pure / 2.8e6 via .sort() | 1.2e6 |
| O(n^2) | 1e6 | 1e12 | 5,700 | 25,000 |
| O(n^3) | 1e9 | — | 340 | 860 |
| O(2^n) | — | — | 24 | 29 |
| O(n!) | — | — | 10 | 11 |
Node’s tight numeric loop is ~30x faster than CPython’s. That gap is why the same O(n^2) solution passes in JS and times out in Python.
Reading the constraint
graph TD
Start["Constraint on n?"] --> Q1{"n ≤ 12"}
Q1 -->|yes| R1["O(n!)<br/>permutations / brute force"]
Q1 -->|no| Q2{"n ≤ 20"}
Q2 -->|yes| R2["O(2^n)<br/>bitmask over subsets"]
Q2 -->|no| Q3{"n ≤ 500"}
Q3 -->|yes| R3["O(n^2) or O(n^3)<br/>nested loops, DP"]
Q3 -->|no| Q4{"n ≤ 1e5"}
Q4 -->|yes| R4["O(n log n)<br/>sort, heap, divide-and-conquer"]
Q4 -->|no| Q5{"n ≤ 1e6"}
Q5 -->|yes| R5["O(n)<br/>single linear pass"]
Q5 -->|no| R6["O(log n) or O(1)<br/>binary search, preprocessing"]
| Constraint | Intended complexity |
|---|---|
| n <= 12 | O(n!) |
| n <= 20 | O(2^n) — bitmask |
| n <= 100 | O(n^4) |
| n <= 500 | O(n^3) |
| n <= 5,000 | O(n^2) |
| n <= 1e5 | O(n log n) |
| n <= 1e6 | O(n) |
| n <= 1e9 | O(log n) or O(1) with preprocessing |
Recursion limits (measured)
| Limit | Fix | |
|---|---|---|
| CPython 3.11 | ~1,000 frames (sys.getrecursionlimit()) | explicit stack, or sys.setrecursionlimit |
| Node 22 / V8 | ~12,500 trivial frames, ~7,000 with 4 args | explicit stack; --stack-size risks a hard crash |
Rule: if n can exceed 1,000 (Python) or 10,000 (Node), write it iteratively.
The doubling test
Double n and look at the ratio. log2(ratio) is the empirical exponent.
| Ratio | Exponent | Class |
|---|---|---|
| ~1.0 | 0 | O(1) |
| ~2.0 | 1 | O(n) |
| ~2.1–2.3 | 1.1 | O(n log n) |
| ~4.0 | 2 | O(n^2) |
| ~8.0 | 3 | O(n^3) |
2. Python quick reference
Built-in complexity
| Operation | Cost |
|---|---|
list append / pop() | O(1) amortized |
list insert(0)/pop(0)/del[0] | O(n) |
list slice a[i:j] | O(j-i) copy |
x in list | O(n) |
list.sort() / sorted() | O(n log n), stable, Timsort |
dict/set get/set/del/in | O(1) avg, O(n) worst |
s & t (set intersection) | O(min(len(s), len(t))) |
s | t, s - t | O(len(s)+len(t)), O(len(s)) |
deque append/appendleft/pop/popleft | O(1) |
deque[i] in the middle | O(n) |
deque.rotate(k) | O(k) |
heapq.heappush/heappop | O(log n) |
heapq.heapify | O(n) |
heapq.nlargest(k, it) | O(n log k) |
bisect_left/bisect_right | O(log n) |
bisect.insort | O(n) (memmove) |
''.join(list) | O(total) |
s += x in a loop | O(n) only if s is a local with refcount 1, else O(n^2) |
Counter(it) / .most_common(k) | O(n) / O(n log k) |
The imports
from collections import deque, defaultdict, Counter, OrderedDict, ChainMap, namedtuple
from heapq import heappush, heappop, heapify, heappushpop, heapreplace, nlargest, nsmallest, merge
from bisect import bisect_left, bisect_right, insort
from itertools import (accumulate, pairwise, groupby, product, permutations, combinations,
chain, islice, tee, cycle, count, zip_longest, starmap)
from functools import cache, lru_cache, reduce, partial, cmp_to_key, cached_property, singledispatch
from dataclasses import dataclass, field, replace
from typing import Optional, Literal, Protocol, TypeVar, Self, TypedDict, NamedTuple
import math, sys, re, operator
The idioms
# containers
grid = [[0]*n for _ in range(m)] # NOT [[0]*n]*m — that aliases
graph = defaultdict(list)
counts = Counter(items)
seen = set()
# queues and heaps
q = deque([start]); q.append(x); q.popleft()
window = deque(maxlen=k) # bounded: appending drops from the front
heappush(h, (priority, next(counter), item)) # counter breaks ties so item is never compared
heappush(h, -value) # max-heap by negation (3.14 adds heappush_max)
# sorting
xs.sort(key=lambda r: (-r.score, r.name)) # desc then asc
xs.sort(key=operator.itemgetter(1)) # ~30% faster than the lambda: stays in C
xs.sort(key=cmp_to_key(cmp)) # last resort
# bisect: five answers from two functions
i = bisect_left(a, x) # lower bound / ceil index / rank
j = bisect_right(a, x) # upper bound
count = j - i # occurrences of x
floor_idx = bisect_left(a, x) - 1
# strings
''.join(parts) # the reliable linear builder
s.startswith(('a', 'b')) # accepts a tuple
s.split() # splits on whitespace runs AND strips
'{:.2f}'.format(x); f'{x:.2f}'; f'{x=}' # f-string debug form (3.8+)
s.translate(str.maketrans(a, b)) # fast char substitution
# iteration
for i, v in enumerate(xs, start=1): ...
for a, b in zip(xs, ys, strict=True): ... # strict= is 3.10+
for a, b in pairwise(xs): ... # adjacent pairs, 3.10+
for x in reversed(xs): ...
list(accumulate(xs)) # prefix sums
list(accumulate(xs, initial=0)) # with the 0 prefix — usually what DP wants
# numbers
divmod(a, b); a // b; -7 // 2 == -4 # floor division, NOT truncation
math.isqrt(n); math.comb(n, k); math.gcd(*xs); math.prod(xs)
float('inf'); float('-inf')
n.bit_length(); n.bit_count() # bit_count is 3.10+
int('ff', 16); bin(5); hex(255); f'{5:08b}'
# unpacking
first, *rest = xs
a, b = b, a # whole RHS evaluated first
*_, last = xs
d = {**d1, **d2}; d = d1 | d2 # 3.9+
# comprehensions
{k: v for k, v in pairs if v}
[y for x in xs for y in x] # flatten (loops read left to right)
next((x for x in xs if pred(x)), None) # first match or None
any(pred(x) for x in xs); all(...) # short-circuit
# memoization
@cache
def dp(i, j): ... # arguments must be hashable
Modern Python by version
| Version | Worth using |
|---|---|
| 3.8 | walrus :=, f-string =, positional-only / |
| 3.9 | dict |, list[int] builtin generics, str.removeprefix |
| 3.10 | match, X | Y unions, zip(strict=), pairwise, bisect(key=) |
| 3.11 | ExceptionGroup/except*, asyncio.TaskGroup, asyncio.timeout, Self, zero-cost try, 10-60% faster |
| 3.12 | PEP 695 type/class C[T], inlined comprehensions, per-interpreter GIL |
| 3.13 | experimental free-threaded build, new REPL, dbm.sqlite3 |
| 3.14 | free-threading officially supported (PEP 779), concurrent.interpreters, t-strings, deferred annotations, heapq *_max, bracketless except A, B: |
3. TypeScript and JavaScript quick reference
Built-in complexity
| Operation | Cost | Mutates |
|---|---|---|
arr[i], arr.push, arr.pop | O(1) | push/pop yes |
arr.shift, arr.unshift | O(n) | yes |
arr.splice(i, d, ...) | O(n) | yes |
arr.slice(a, b) | O(b-a) | no |
arr.indexOf / includes / find | O(n) | no |
arr.sort | O(n log n), stable TimSort | yes |
arr.toSorted/toReversed/with/toSpliced | O(n) | no (ES2023) |
Map/Set get/set/has/delete | O(1) avg | — |
Set.union/intersection/difference | O(n+m)/O(min)/O(n) | no (ES2025) |
Object.keys/values/entries | O(n) | no |
{...obj} / Object.assign | O(n), invokes getters | no |
str + str | O(1) (rope) | — |
str.slice/substring | O(1) (view) | — |
str.split('') | O(n) allocations — avoid | — |
JSON.parse/stringify | O(size) | — |
structuredClone | O(size), handles cycles | no |
The idioms
// containers
const grid = Array.from({ length: m }, () => new Array<number>(n).fill(0)); // packed, no aliasing
const seen = new Set<string>();
const counts = new Map<string, number>();
counts.set(k, (counts.get(k) ?? 0) + 1);
const groups = Object.groupBy(items, x => x.kind); // ES2024
const byTeam = Map.groupBy(items, x => x.teamId); // any key type
// queue: NEVER shift()
const q = [start]; for (let head = 0; head < q.length; head++) { const v = q[head]!; /* ... */ }
// sorting
xs.sort((a, b) => a - b); // numbers — the default is lexicographic!
xs.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name)); // multi-key via ||
const coll = new Intl.Collator('es', { numeric: true }); xs.sort(coll.compare);
const sorted = xs.toSorted((a, b) => a - b); // non-mutating
// strings
[...str] // code POINTS (handles emoji)
str.length // code UNITS
str.at(-1); str.replaceAll(a, b); str.padStart(2, '0')
// iteration
for (const [i, v] of xs.entries()) {}
for (const k of map.keys()) {}
xs.flatMap(f); xs.flat(Infinity); xs.at(-1); xs.findLast(p);
xs.reduce((acc, v) => acc + v, 0);
naturals().map(f).filter(p).take(5).toArray(); // lazy iterator helpers (ES2026)
// numbers and bits
Math.trunc(-7 / 2) === -3; Math.floor(-7 / 2) === -4; // JS / truncates toward zero via trunc
(a + b) >>> 1 // unsigned mid
32 - Math.clz32(n) // bit length
Math.imul(a, b) // 32-bit multiply
x >>> 0 // force unsigned — matters for hashing
Number.isInteger, Number.MAX_SAFE_INTEGER, Number.EPSILON
BigInt(n); 10n ** 30n
// objects
Object.hasOwn(o, k); Object.create(null); // dictionary with no prototype
Object.fromEntries(map); Object.entries(o);
const { a = 1, b: { c } = {}, ...rest } = obj;
obj?.a?.[0]?.(); opts.retries ??= 3;
// async
await Promise.all([a(), b()]); // parallel
for (const x of xs) await f(x); // sequential — usually not what you want
await Promise.allSettled(ps); // never rejects
AbortSignal.timeout(1000); AbortSignal.any([s1, s2]);
using conn = open(); // Symbol.dispose (ES2026)
TypeScript type quick reference
// narrowing
if (typeof x === 'string') {}
if (x instanceof Date) {}
if ('kind' in x) {}
if (x != null) {} // != catches null AND undefined
function isT(x: unknown): x is T { ... } // type guard
function assertT(x: unknown): asserts x is T { ... } // assertion function
default: { const _e: never = x; } // exhaustiveness
// utilities
Partial<T> Required<T> Readonly<T> Record<K,V> Pick<T,K> Omit<T,K>
Exclude<T,U> Extract<T,U> NonNullable<T> Awaited<T> NoInfer<T>
Parameters<F> ReturnType<F> ConstructorParameters<C> InstanceType<C>
Uppercase<S> Lowercase<S> Capitalize<S> Uncapitalize<S>
// the ones you write yourself
type Prettify<T> = { [K in keyof T]: T[K] } & {};
type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;
type Mutable<T> = { -readonly [K in keyof T]: T[K] };
type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
type IsNever<T> = [T] extends [never] ? true : false;
type IsAny<T> = 0 extends (1 & T) ? true : false;
// the three ways to attach a type
const a: Config = v; // annotation — widens to Config
const b = v satisfies Config; // check, keep the narrow inferred type <- usually what you want
const c = v as Config; // assertion — can lie
// key facts
// - method-shorthand params are BIVARIANT; function-property params are CONTRAVARIANT
// - arrays are covariant (unsound); use readonly T[] when you only read
// - conditional types distribute over naked type params; wrap in [T] to stop it
// - interface can merge and reports extends conflicts eagerly; type & defers to never
Modern JS/TS by version
| Edition | Worth using |
|---|---|
| ES2020 | ?., ??, BigInt, Promise.allSettled, globalThis |
| ES2021 | replaceAll, Promise.any, ??=/||=/&&=, WeakRef |
| ES2022 | top-level await, at(), Object.hasOwn, class #private/static blocks, Error.cause |
| ES2023 | toSorted/toReversed/with/toSpliced, findLast |
| ES2024 | Object.groupBy/Map.groupBy, Promise.withResolvers, ArrayBuffer.transfer |
| ES2025 | Set methods, iterator helpers, Array.fromAsync, RegExp.escape, import attributes |
| ES2026 | Promise.try, Error.isError, Float16Array, Math.sumPrecise, Map.getOrInsert, Temporal, using |
| TS 5.x | satisfies, const type params, NoInfer, standard decorators, verbatimModuleSyntax |
| TS 6.0 | final JS-based release (Mar 2026); deprecation warnings for what 7.0 removes |
| TS 7.0 | native Go compiler (~10x faster), strict on by default, drops ES5/downlevelIteration/node10/AMD-UMD/baseUrl, adds --checkers/--builders |
Node 22 availability (feature-detected): iterator helpers, Set methods, Object.groupBy,
Array.fromAsync, Symbol.dispose, toSorted family — yes. Promise.try, RegExp.escape,
Error.isError, Float16Array, Math.sumPrecise, Temporal, Map.getOrInsert — no.
4. Side-by-side: the same thing in both languages
| Task | Python | TypeScript |
|---|---|---|
| Queue | deque(), popleft() | array + head cursor, or a ring buffer |
| Min-heap | heapq on a list | hand-rolled MinHeap (no built-in) |
| Max-heap | negate, or 3.14 heappush_max | comparator inversion |
| Sorted container | bisect on a list, sortedcontainers | sorted array + binary search, or an AVL |
| Counter | Counter(xs) | Map + get(k) ?? 0, or Object.groupBy |
| Grouping | defaultdict(list) | Map.groupBy(xs, f) |
| Set algebra | a & b, a | b, a - b | a.intersection(b), .union, .difference |
| Memoize | @cache | a Map keyed on a serialized state |
| Tuple key | d[(i, j)] | map.set(\${i},${j}`, v)or a nestedMap` |
| Immutable record | @dataclass(frozen=True, slots=True) | readonly fields + Object.freeze |
| Structural interface | Protocol | interface (structural by default) |
| Enum | Enum / StrEnum / Literal[...] | union of literals, or as const object |
| Exhaustive switch | match + assert_never | switch + const _: never |
| Integer division | a // b (floors) | Math.trunc(a / b) or (a / b) | 0 |
| Big integers | int (unbounded by default) | BigInt |
| Bit set | plain int | BigInt or Uint32Array |
| String build | ''.join(parts) | parts.join('') (or +=, which is a rope in V8) |
| Char iteration | for ch in s (code points) | for (const ch of s) (code points) |
| Lazy pipeline | generators | generators + iterator helpers |
| Async fan-out | asyncio.gather / TaskGroup | Promise.all / allSettled |
| Bounded concurrency | asyncio.Semaphore | a pMap worker pool |
| Cancellation | task.cancel() / asyncio.timeout | AbortController / AbortSignal.timeout |
| Type-check a value at runtime | isinstance, pydantic | a type guard, zod |
| Deep clone | copy.deepcopy | structuredClone |
| Format | f-strings | template literals |
5. Algorithm templates
Binary search — two templates, and everything derives from them
# exact match, inclusive bounds
lo, hi = 0, len(a) - 1
while lo <= hi:
m = lo + (hi - lo) // 2
if a[m] == t: return m
if a[m] < t: lo = m + 1
else: hi = m - 1
return -1
# boundary / first-true, exclusive upper bound <- default to this one
lo, hi = 0, len(a)
while lo < hi:
m = (lo + hi) // 2
if a[m] < t: lo = m + 1 # `<` -> lower_bound; `<=` -> upper_bound
else: hi = m
return lo
# binary search the ANSWER (monotone predicate)
lo, hi = min_possible, max_possible
while lo < hi:
m = lo + (hi - lo) // 2
if feasible(m): hi = m
else: lo = m + 1
return lo
Sliding window
left = 0
for right, v in enumerate(a):
add(v)
while invalid(): # for LONGEST: shrink while invalid, record AFTER
remove(a[left]); left += 1
best = max(best, right - left + 1)
left = 0
for right, v in enumerate(a):
add(v)
while valid(): # for SHORTEST: shrink while valid, record INSIDE
best = min(best, right - left + 1)
remove(a[left]); left += 1
Monotonic stack
st = []
for i, v in enumerate(a):
while st and a[st[-1]] < v: # `<` -> next greater; `>` -> next smaller
j = st.pop()
answer[j] = ... # the POP is when you learn j's answer
st.append(i)
BFS / DFS / topological sort
# BFS with levels
q = deque([start]); seen = {start}; dist = {start: 0}
while q:
v = q.popleft()
for n in adj[v]:
if n not in seen:
seen.add(n); dist[n] = dist[v] + 1; q.append(n)
# BFS by explicit levels (for "minutes elapsed" style answers)
while q:
for _ in range(len(q)):
v = q.popleft(); ...
steps += 1
# iterative DFS
st = [start]; seen = set()
while st:
v = st.pop()
if v in seen: continue
seen.add(v)
st.extend(n for n in adj[v] if n not in seen)
# Kahn topological sort
indeg = [0]*n
for u, v in edges: adj[u].append(v); indeg[v] += 1
q = deque(i for i in range(n) if indeg[i] == 0); order = []
while q:
v = q.popleft(); order.append(v)
for nx in adj[v]:
indeg[nx] -= 1
if indeg[nx] == 0: q.append(nx)
return order if len(order) == n else None # None -> cycle
Dijkstra
dist = {s: 0}; pq = [(0, s)]
while pq:
d, v = heappop(pq)
if d > dist.get(v, inf): continue # stale entry
for n, w in adj[v].items():
nd = d + w
if nd < dist.get(n, inf):
dist[n] = nd; heappush(pq, (nd, n))
Union-Find
parent = list(range(n)); size = [1]*n
def find(x):
while parent[x] != x: parent[x] = parent[parent[x]]; x = parent[x]
return x
def union(a, b):
ra, rb = find(a), find(b)
if ra == rb: return False # already connected -> a cycle
if size[ra] < size[rb]: ra, rb = rb, ra
parent[rb] = ra; size[ra] += size[rb]
return True
Backtracking
def go(state):
if is_solution(state): res.append(snapshot(state)); return
if cannot_lead_anywhere(state): return # PRUNE — this is where the speed is
for choice in choices(state):
if i > start and choice == prev: continue # skip duplicate SIBLINGS
apply(choice)
go(state)
undo(choice)
DP
# top-down: write the brute force, then add one decorator
@cache
def dp(i, j):
if base_case: return base_value
return best(dp(i-1, j), dp(i, j-1) + cost)
# 0/1 knapsack: capacity loop BACKWARD
for w, v in items:
for c in range(cap, w-1, -1): dp[c] = max(dp[c], dp[c-w] + v)
# unbounded: capacity loop FORWARD
for c in range(1, cap+1):
for w, v in items:
if w <= c: dp[c] = max(dp[c], dp[c-w] + v)
# count COMBINATIONS: item loop outside. count PERMUTATIONS: target loop outside.
Tree recursion
def go(node):
if not node: return identity
left, right = go(node.left), go(node.right)
best[0] = max(best[0], combine_through(node, left, right)) # global answer
return report_upward(node, left, right) # what the parent can use
6. Data structure choice
| I need | Use | Cost |
|---|---|---|
| membership | set / Set | O(1) |
| key -> value | dict / Map | O(1) |
| counts | Counter / Map | O(1) per update |
| LIFO | list / array push+pop | O(1) |
| FIFO | deque / ring buffer | O(1) |
| both ends | deque | O(1) |
| repeated min/max | binary heap | O(log n) |
| top k of n | heap of size k | O(n log k) |
| kth once | quickselect | O(n) expected |
| running median | two heaps | O(log n) |
| ordered + mutable | balanced tree / skip list / sortedcontainers | O(log n) |
| ordered + read-heavy | sorted array + binary search | O(1) index, O(n) insert |
| floor/ceil/rank/count | bisect on a sorted array, or a Fenwick tree | O(log n) |
| prefix / autocomplete | trie | O(L) |
| connectivity under merges | union-find | O(alpha(n)) |
| range sum, static | prefix sums | O(1) query |
| range query + point update | Fenwick (invertible) / segment tree (any monoid) | O(log n) |
| range query + range update | segment tree with lazy propagation | O(log n) |
| bounded cache | LRU (linked list + map) | O(1) |
| probable membership, tiny space | Bloom filter | ~9.6 bits/element at 1% FP |
| approximate frequencies | Count-Min Sketch | fixed memory |
| approximate distinct count | HyperLogLog | ~1.5 KB for 1e9 |
| metadata on objects, no leak | WeakMap | O(1) |
| numeric bulk data | array/NumPy / typed arrays | 4-10x less memory |
7. The trap list
Everything here has been measured or compiler-verified elsewhere in this guide.
Both languages
- Nested loop where the inner pointer never resets is O(n), not O(n^2). Count total iterations.
x in listinside a loop is the most common accidental O(n^2). Hoist to a set — measured 250x at n=4000.- Recursion over n > 1,000 (Python) or n > 10,000 (Node) overflows. Go iterative.
- Slicing/spreading inside a loop adds a hidden O(n) factor. Pass indices.
- Validating a BST against immediate children only. Use in-order or (lo, hi) bounds.
- Dijkstra with a negative edge is silently wrong, not an error.
- Floyd-Warshall needs
kas the outermost loop. - 0/1 knapsack needs the capacity loop backward.
- Prefix-sum counting needs the
seen[0] = 1seed. - Dedup in backtracking uses
i > start, noti > 0. - Marking BFS visited on dequeue instead of enqueue makes the queue O(E).
Python-specific
list.pop(0)/insert(0, x)is O(n) — measured 145x slower thandequeat n=32k.[[0]*n]*maliases one inner list.s += xin a loop is linear only whensis a local with refcount 1.''.joinalways.bisect.insortis O(n) — building a sorted list with it is O(n^2).- Mutable default arguments are created once, at definition.
defaultdictcreates the key when you read it. Use.get()to peek.- Bare
except:swallowsKeyboardInterrupt,SystemExit,CancelledError. @lru_cacheon a method keeps everyselfalive forever.returninsidefinallydiscards the exception and the original return value.-7 // 2 == -4(floors), not -3.Countersubtraction clamps at 0; use.subtract()for negatives.itertools.groupbygroups only consecutive equal keys — sort first.- Mutating a list while iterating it forward silently skips elements.
JavaScript / TypeScript-specific
[10, 9, 1].sort()is[1, 10, 9]— the default comparator is lexicographic.arr.shift()is O(n). Use a head cursor.delete arr[i]creates a hole and permanently deoptimizes the array (measured 40% slower).new Array(n)without.fill()is holey from birth.Array(n).fill([])shares one inner array.awaitinsideforEachdoes nothing —forEachignores the returned promise.for awaitover an array of promises is sequential;Promise.allis parallel.parseInt('08')is fine now, butparseInt(x, 10)is still the habit;Number(x)for full strings.typeof null === 'object';NaN !== NaN;0.1 + 0.2 !== 0.3.str.lengthcounts UTF-16 code units —'😀'.length === 2. Use[...str].- Bitwise operators coerce to 32-bit signed.
x >>> 0for unsigned; missing it caused real false negatives in the Bloom filter in this guide. Object.keysreturns integer-like keys first, in ascending numeric order.- An object used as a dictionary with churning keys goes into slow dictionary mode — use
Map. - Method-shorthand parameters are bivariant; declare callbacks as properties for contravariant checking.
- Arrays are covariant in TypeScript —
readonly T[]when you only read. Omitis not homomorphic:Omiton a tuple gives you an object.
8. Interview phrasebook
Sentences that carry signal. Say the underlined idea, not the word.
Opening
- “Before I code — what’s the size of n, can the input have duplicates or negatives, and am I allowed to mutate it?”
- “Let me restate: given X, return Y, and the constraint is Z. Is that right?”
Choosing an approach
- “The brute force is O(n^2) because it recomputes the sum for every window. The redundancy is the overlap, so a sliding window removes it.”
- “This is contiguous plus a monotone constraint, so sliding window. If the values could be negative the window stops being monotone and I’d switch to prefix sums with a hash map.”
- “I’m using a heap rather than a sort because I only need the extremum, not the order.”
- “I’m using a
Maprather than an object because the keys are dynamic.” - “n <= 20 in the constraints, so I’m reading that as bitmask over subsets.”
Complexity
- “n is the number of nodes and m the number of edges. Time O(n + m), space O(n) for the queue.”
- “Each element is pushed and popped at most once, so the total work in the while loop is O(n) even though it’s nested.”
- “That’s Theta(n log n) and it’s tight — the sort dominates and I can’t avoid needing the order.”
- “The n log n is only from sorting. If the input arrived sorted this would be linear.”
- “Amortized O(1), not worst-case O(1) — one insert can still trigger an O(n) resize, which matters if you have a latency budget.”
Trade-offs
- “Two options: quickselect is O(n) expected but mutates the input; a size-k heap is O(n log k) and works on a stream. Which matters more here?”
- “I’d ship the O(n^2) version if n is under a few thousand — it’s half the code and much easier to get right.”
- “This is O(1) expected. The worst case is O(n) with adversarial keys, which is a real DoS vector, and the mitigation is a randomized hash seed.”
When stuck
- “Let me try a small example by hand and see what the answer should be.”
- “I know how to do this in O(n^2). Let me get that correct first and then optimize.”
- “What I need is ‘the nearest larger element to the right’ — that’s a monotonic stack.”
- “I’m not sure this greedy is safe. Let me look for a counterexample.” (Then find one, or argue the exchange.)
Answering “can you do better?”
- “Not asymptotically — any comparison-based approach is Omega(n log n) by the decision-tree argument. But if the keys are bounded integers, counting sort makes it O(n + k).”
- “Yes: the repeated max is the bottleneck, and a monotonic deque makes it O(1) amortized instead of O(k).”
- “Only with different assumptions — O(1) if I can preprocess, or O(log n) if I keep it sorted as it arrives.”
Closing
- “Edge cases I’d test: empty input, one element, all equal, all distinct, and the maximum n.”
- “If this were production code I’d add [X], and the thing most likely to break is [Y].”
Next: Study plan and flashcards, or back to the index.