Frontend system design: JavaScript internals (closures, event loop, promises, async/await)
An interview theme bundling JS internals: closures (functions remembering their lexical scope), the event loop (single thread + queues scheduling async callbacks), Promises (objects representing future values with microtask reactions), and async/await (sugar over Promises). Expect output-prediction puzzles and 'explain how X works under the hood'.
This is a cluster topic — interviewers probe several JS internals in sequence. Here's a connected map of all four.
1. Closures
A closure is a function bundled with a reference to its lexical scope. The inner function "remembers" variables from where it was defined, even after the outer function returns.
function counter() {
let count = 0;
return () => ++count; // closes over `count`
}
const next = counter();
next(); next(); // 1, 2 — `count` lives on via the closureUsed for: data privacy, function factories, memoization, hooks (useState is closures).
2. The event loop
JS is single-threaded with one call stack. Async work goes to Web APIs; completed callbacks queue up. The event loop drains all microtasks, then runs one macrotask, whenever the stack is empty.
- Microtasks: Promise reactions,
awaitcontinuations,queueMicrotask. - Macrotasks:
setTimeout, events, I/O.
3. Promises
A Promise is an object representing a value that will exist later. States: pending → fulfilled or rejected (settled, immutable after).
const p = new Promise((resolve, reject) => { /* ... */ });
p.then(onFulfilled).catch(onError).finally(cleanup);.then/.catch callbacks run as microtasks. Combinators: Promise.all (all or first reject), allSettled (wait for all), race (first settle), any (first fulfill).
4. async/await
Syntax sugar over Promises. An async function returns a Promise; await pauses it and schedules the continuation as a microtask when the awaited value settles. try/catch handles rejections.
How they connect
function makeFetcher(baseURL) { // closure over baseURL
return async (path) => { // async/await
const res = await fetch(baseURL + path); // Promise + event loop scheduling
return res.json();
};
}
const api = makeFetcher("/v1");One small example uses all four: a closure captures config, the returned function is async, fetch returns a Promise, and await continuations are microtasks scheduled by the event loop.
Senior framing
The interviewer wants to see these aren't isolated trivia — they're one system. Closures explain how state persists, the event loop explains when code runs, Promises explain how future values are represented, and async/await is the ergonomic layer on top. Be ready for output-order puzzles that combine setTimeout, Promise.then, and await.
Follow-up questions
- •How does `useState` rely on closures?
- •Walk through the output of a mixed setTimeout/Promise/await snippet.
- •What's the difference between Promise.all and Promise.allSettled?
Common mistakes
- •Treating these four topics as unrelated facts.
- •Closure bugs with `var` in loops.
- •Mispredicting microtask vs macrotask order.
- •Not handling Promise rejections.
Edge cases
- •Closures over loop variables (var vs let).
- •await continuations are microtasks even for non-Promise values.
Real-world examples
- •Custom hooks, debounce/throttle, data-fetching layers, memoization.