JavaScript/TypeScript Interview Questions 2025 — Closures, Event Loop, Promises, TypeScript Generics, React, Node.js

JavaScript is the language of the web, powering frontend (React, Vue), backend (Node.js, Deno, Bun), and full-stack applications. TypeScript has become the default for production JavaScript. This guide covers the language-specific questions tested at frontend and full-stack interviews — from closures and the event loop to TypeScript generics and React internals.

Closures and Scope

A closure is a function that remembers the variables from its enclosing scope even after the outer function has returned. function counter() { let count = 0; return { increment: () => ++count, getCount: () => count }; }. The returned object references count from the outer scope. Even though counter() has returned, count persists because the inner functions close over it. Use cases: (1) Data privacy — the count variable is not accessible from outside. Only increment and getCount can access it. This is the module pattern. (2) Factory functions — function createMultiplier(x) { return (y) => x * y; }. createMultiplier(3) returns a function that triples any input. (3) Event handlers — function setupButton(message) { button.addEventListener(“click”, () => alert(message)); }. The handler closes over message. Common pitfall: closures in loops. for (var i = 0; i console.log(i), 100); }. Prints 5 five times (var is function-scoped; all callbacks share the same i). Fix: use let (block-scoped): for (let i = 0; i console.log(j), 100); })(i). Interview question: “What will this code print?” is the classic closure question. Understand var vs let scoping and how closures capture variables by reference, not by value.

The Event Loop and Async Model

JavaScript is single-threaded with an event loop. The event loop processes: (1) Call stack — synchronous code executes here. One function at a time. (2) Microtask queue — Promise callbacks (.then, .catch, .finally) and queueMicrotask. Processed after the current task and before the next macrotask. (3) Macrotask queue — setTimeout, setInterval, I/O callbacks, UI rendering. One macrotask is processed per event loop iteration. Execution order: current call stack completes -> all microtasks are drained -> one macrotask is processed -> repeat. Example: console.log(“A”); setTimeout(() => console.log(“B”), 0); Promise.resolve().then(() => console.log(“C”)); console.log(“D”). Output: A, D, C, B. Synchronous A and D execute first (call stack). Promise callback C runs next (microtask). setTimeout B runs last (macrotask), even with delay 0. Why this matters: understanding the event loop is essential for debugging async behavior, avoiding UI freezes (long synchronous operations block the event loop), and writing correct async code. Node.js event loop: extends the browser model with additional phases: timers, pending callbacks, poll (I/O), check (setImmediate), and close callbacks. process.nextTick runs before any phase (even before microtasks in Node). Interview question: “Explain the order of execution” for code mixing sync, Promise, setTimeout, and async/await.

Promises and Async/Await

Promises represent a future value. States: pending, fulfilled (resolved with a value), or rejected (with an error). Chaining: fetch(url).then(res => res.json()).then(data => process(data)).catch(err => handle(err)). Each .then returns a new Promise, enabling chaining. async/await: syntactic sugar over Promises. async function getData() { try { const res = await fetch(url); const data = await res.json(); return process(data); } catch (err) { handle(err); } }. The await keyword pauses execution until the Promise resolves, but does NOT block the thread (the event loop continues processing other tasks). Concurrent await: const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]). All three requests run concurrently. If any rejects: Promise.all rejects. For partial results: use Promise.allSettled (returns all results, both fulfilled and rejected). Promise.race: resolves/rejects with the first settled promise. Use for: timeout patterns (race the request against a timer). Error handling: unhandled promise rejections crash Node.js (since v15). Always .catch() or try/catch with await. Common mistake: forgetting to await an async function (the function returns a Promise, not the resolved value). Interview question: “What is the difference between Promise.all, Promise.allSettled, and Promise.race?” All: all succeed or first failure. AllSettled: all complete regardless. Race: first to complete.

TypeScript: Types, Generics, and Utility Types

TypeScript adds static typing to JavaScript. Catching bugs at compile time instead of runtime. Key type features: (1) Union types: string | number. The variable can be either type. Use type narrowing (typeof, instanceof, discriminated unions) to work with specific types. (2) Generics: function identity<T>(arg: T): T { return arg; }. Works with any type while preserving type information. Array<T>, Map<K, V>, Promise<T> are generic. (3) Discriminated unions: type Shape = { kind: “circle”; radius: number } | { kind: “rect”; width: number; height: number }. switch(shape.kind) enables exhaustive type-safe handling. (4) Utility types: Partial<T> (all properties optional), Required<T> (all required), Pick<T, K> (select properties), Omit<T, K> (exclude properties), Record<K, V> (object with key type K and value type V), and Readonly<T> (immutable). (5) Type inference: TypeScript infers types from context. const x = 5; // x is number. const arr = [1, 2, 3]; // arr is number[]. Explicit types are needed only when inference is insufficient. (6) Zod: runtime type validation (like Pydantic for Python). Define a schema, parse input, get typed output. Integration with React Hook Form and API validation. Interview question: “Explain the difference between type and interface in TypeScript.” Answer: both define object shapes. Interfaces support declaration merging (extending across files) and are better for public APIs. Types support unions, intersections, and mapped types. For objects: either works. For complex types: use type.

React and Node.js Essentials

React: (1) Hooks — useState (local state), useEffect (side effects / lifecycle), useContext (shared state), useMemo (memoized computation), useCallback (memoized function reference), and useRef (mutable ref that persists across renders). (2) Virtual DOM — React maintains an in-memory representation of the DOM. On state change: compute the new virtual DOM, diff against the previous (reconciliation), and apply only the changed DOM nodes (minimal DOM manipulation). This is faster than directly manipulating the DOM for complex UIs. (3) Server Components (React 19+) — components that render on the server and send HTML to the client. No JavaScript bundle for server components. Reduces client bundle size and initial load time. “use client” marks components that need client-side interactivity. Node.js: (1) Non-blocking I/O — all I/O operations (file, network, database) are asynchronous by default. The event loop handles thousands of concurrent connections on a single thread. (2) Streams — process data in chunks without loading the entire content into memory. Readable, Writable, Transform, and Duplex streams. fs.createReadStream(“large-file.csv”).pipe(csvParser()).pipe(processRow()). Memory efficient for large files. (3) Worker threads — for CPU-bound tasks, offload to a worker thread (separate V8 instance with its own event loop). Communicate via message passing. Use for: image processing, encryption, and heavy computation. (4) Express vs Fastify — Express is the standard framework (simple, huge ecosystem). Fastify is 2-3x faster (schema-based validation, built-in JSON serialization). Hono is the modern lightweight alternative.

{“@context”:”https://schema.org”,”@type”:”FAQPage”,”mainEntity”:[{“@type”:”Question”,”name”:”How does the JavaScript event loop work?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”JavaScript is single-threaded with an event loop processing three queues: (1) Call stack — synchronous code. One function at a time. (2) Microtask queue — Promise callbacks (.then/.catch), queueMicrotask. Drained completely after current task, before macrotasks. (3) Macrotask queue — setTimeout, setInterval, I/O. One macrotask per loop iteration. Order: call stack completes -> ALL microtasks drain -> ONE macrotask -> repeat. Example: console.log(A); setTimeout(()=>console.log(B),0); Promise.resolve().then(()=>console.log(C)); console.log(D). Output: A, D, C, B. Sync first (A,D), then microtask (C), then macrotask (B). setTimeout 0 does NOT mean immediate — it means next macrotask (after all microtasks). Understanding this is essential for debugging async behavior and avoiding UI freezes.”}},{“@type”:”Question”,”name”:”What is the difference between Promise.all, Promise.allSettled, and Promise.race?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Promise.all(promises): resolves when ALL promises resolve. Rejects immediately when ANY promise rejects. Use when: you need all results and any failure is fatal (parallel API calls where all are required). Promise.allSettled(promises): resolves when ALL promises settle (fulfilled or rejected). Returns array of {status, value/reason} for each. Never rejects. Use when: you want all results regardless of individual failures (batch operations where some may fail). Promise.race(promises): resolves/rejects with the FIRST promise to settle. Use for: timeout patterns (race a request against a timer — if timer wins, the request timed out), and caching (race cache lookup against network fetch — return whichever completes first). All three run promises concurrently. The difference is how they handle completion and failure. Common pattern: const result = await Promise.race([fetchData(), timeout(5000)]); — if fetchData takes longer than 5 seconds, timeout rejects first.”}}]}
Scroll to Top