Back to JavaScript
JavaScript
hard
mid

How would you explain JavaScript internals like closures, the event loop, Promises, and async/await in an interview?

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'.

7 min read·~12 min to think through

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.

js
function counter() {
  let count = 0;
  return () => ++count;   // closes over `count`
}
const next = counter();
next(); next(); // 1, 2 — `count` lives on via the closure

Used 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, await continuations, queueMicrotask.
  • Macrotasks: setTimeout, events, I/O.

3. Promises

A Promise is an object representing a value that will exist later. States: pendingfulfilled or rejected (settled, immutable after).

js
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

js
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.

Related questions