Skip to content

TypeScript / Node.js — Theory

JS on one thread. Blocking ops (I/O, crypto, DNS) → libuv. Network = OS async (epoll/kqueue/IOCP). File + DNS = thread pool. CPU-bound work blocks every request → use worker threads, child processes, or offload.

  • setImmediate vs setTimeout(fn, 0): from main script = nondeterministic. From I/O callback = setImmediate always first.
  • nextTick vs Promise: nextTick before Promises. Both between phases.
  • Poll phase: blocks waiting for I/O if no timers/setImmediate queued.
  • V8: young (scavenge) + old (mark-sweep + compact). Generational GC.
  • Default cap ~1.5GB on 64-bit. Raise: --max-old-space-size=4096.
  • Leaks: unbounded caches, listener leaks, closures holding refs, timers never cleared.
  • Diagnose: --inspect, Chrome DevTools heap snapshot, clinic.js, --heapsnapshot-signal=SIGUSR2.
  • Cluster: master forks N workers per CPU; share port via OS. Each worker own V8. Stateless HTTP.
  • Worker threads: own V8 isolate, message-passing or SharedArrayBuffer. CPU-bound work.
  • Child process: full new Node process. Heaviest.
  • unknown vs any: unknown forces narrow. Use in catch(e: unknown) (TS 4.4+).
  • Discriminated union: literal tag kind. Switch narrows. Better than class hierarchy.
  • Generic constraint: <T extends { id: string }> enforces shape.
  • infer: type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T.
  • Mapped modifiers: +?/-?, +readonly/-readonly, as rename keys.
  • Variance: params contravariant (with strictFunctionTypes), return covariant.
  1. Walk app.get('/x') lifecycle: TCP accept → llhttp → router → middleware → response → flush.
  2. Why await in for serializes — parallelize with Promise.all.
  3. Debug event loop lag: monitorEventLoopDelay, perf_hooks, clinic doctor.
  4. child_process.fork vs worker_threads.Worker — process boundary vs thread/isolate.
  5. Promise rejection in Node 15+ — exits by default.
  6. Type pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>.
  7. Graceful shutdown: drain in-flight, close DB/Redis, exit.
  • CJS/ESM interop fragility.
  • Arrow this lexical vs regular dynamic.
  • JSON.stringify on circular ref throws.
  • Race on async init — share single init() Promise.
  • Pre-Node 15 unhandled rejections silently lost.