· career · 9 min read
Top 10 JavaScript Interview Questions That Will Stun Candidates in 2025
A practical, interview-focused deep dive into 10 surprising and challenging JavaScript questions hiring managers might use in 2025 - each with the interviewer’s intent, a recommended answer outline, pitfalls, and follow-ups.

Outcome-first: by the time you finish this article you’ll be able to both ask and answer ten advanced JavaScript questions that separate a competent candidate from a great one. You’ll know what interviewers are probing for - mental models, trade-offs, and failure modes - and you’ll have concise, high-impact ways to explain your reasoning.
Why these questions matter (quick)
Interviewers in 2025 are looking for more than syntax recall. They want: mental models of runtime behavior, reasoning about concurrency and memory, the ability to explain trade-offs, and a knack for spotting brittle code. These ten questions are designed to test those skills.
1) “Design a tiny reactive system from scratch - dependency tracking, subscription, and update propagation.”
Why ask it
- Tests systems design, knowledge of ES features (Proxy, closures), and ability to reason about efficient updates.
What the interviewer looks for
- Clear separation of dependency tracking and notification.
- Avoiding unnecessary recomputation.
- Handling nested reads and re-entrant updates.
Recommended answer outline (short implementation sketch)
- Use a global (or stack) to track the currently evaluating computation.
- Wrap source objects with a Proxy that records property access into the current computation’s dependency set.
- When a property changes, notify dependent computations.
Example (minimal):
// very small reactive core
const activeStack = [];
const deps = new WeakMap(); // target -> prop -> Set(effect)
function reactive(obj) {
return new Proxy(obj, {
get(target, prop) {
const effect = activeStack[activeStack.length - 1];
if (effect) {
let map = deps.get(target) || new Map();
deps.set(target, map);
let s = map.get(prop) || new Set();
map.set(prop, s);
s.add(effect);
}
return Reflect.get(target, prop);
},
set(target, prop, value) {
const res = Reflect.set(target, prop, value);
const map = deps.get(target);
if (map) {
const s = map.get(prop);
if (s) s.forEach(fn => fn());
}
return res;
},
});
}
function effect(fn) {
const wrapped = () => {
activeStack.push(wrapped);
try {
fn();
} finally {
activeStack.pop();
}
};
wrapped();
}Pitfalls to mention
- Memory leaks if dependencies aren’t cleaned when effects are removed.
- Infinite loops from effects that synchronously write to tracked properties.
- Batching updates for performance.
Follow-ups
- How would you batch updates? (microtask queuing).
- How to support async effects? (track begun/finished state; cancelable tokens).
2) “Explain the exact ordering of tasks: microtasks, macrotasks, rendering breaks - and show a case where Node and browsers differ.”
Why ask it
- Reveals deep understanding of the event loop and the scheduling model, and the ability to reason about timing-sensitive bugs.
What the interviewer looks for
- Correct distinction between microtasks (Promise callbacks, MutationObserver) and macrotasks (setTimeout, I/O callbacks).
- Awareness that Node has phases and that some behaviors differ between environments.
Answer outline + demonstrative example
- Microtasks run immediately after the current call stack finishes and before the next macrotask.
- In browsers: Promise.then → MutationObserver → rendering → macrotask.
- In Node: process.nextTick runs before Promise microtasks; timers phase ordering and I/O callbacks differ.
Example that surprises people:
console.log('start');
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
console.log('end');
// Browser output: start, end, promise, timeout
// In Node: start, end, promise, timeout - but process.nextTick would run before the promise microtask.Show Node nuance:
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
// Node: nextTick, promisePitfalls
- Assuming setTimeout(…, 0) is «immediate».
- Not accounting for rendering frames (rAF) in browsers.
References
- Jake Archibald’s event loop article: https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
- Node.js docs about the event loop: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
3) “WeakRef and FinalizationRegistry - when are they useful and why should you be cautious?”
Why ask it
- Knowledge of modern memory primitives and safe patterns for caches, without relying on finalizers.
What the interviewer looks for
- Understanding that finalizers run non-deterministically and may never run before process exit.
- Awareness that WeakRef can reintroduce races and should be used only for caches or heuristics.
Answer summary + example
- Use WeakRef for caches where you prefer to keep an object alive but don’t require it. Use FinalizationRegistry for cleanup that’s best-effort, not required.
Example:
const registry = new FinalizationRegistry(id => {
// best-effort cleanup, not guaranteed on exit.
console.log('clean up', id);
});
let obj = { name: 'keep' };
const ref = new WeakRef(obj);
registry.register(obj, 'obj-1');
obj = null; // now eligible for GCPitfalls to mention
- Never use FinalizationRegistry for program correctness (e.g., releasing DB connections) - it’s non-deterministic.
- WeakRef can lead to tearing: the referent may become undefined at any point.
Reference
- MDN WeakRef & FinalizationRegistry: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakRef
4) “Using the Temporal API, compute the next business day at 09:00 local time for arbitrary time zones, handling DST transitions.”
Why ask it
- Temporal is modern and safer than Date. This question tests understanding of calendrical correctness and DST edge cases.
What the interviewer looks for
- Use of Temporal rather than Date.
- Awareness of ambiguous or missing local times during DST transitions.
- Handling weekend rollover and holidays (if you extend the problem).
Answer sketch
- Use Temporal.Now.plainDateISO(zone) to get the current local date in a zone.
- Increment the date until it’s a weekday.
- Create a ZonedDateTime at 09:00 and, if that local time is invalid due to DST spring-forward, decide policy (earliest valid, skip, or map to 09:00+ offset).
Pseudo-code:
import { Temporal } from '@js-temporal/polyfill';
function nextBusinessDayAtNine(zone) {
let date = Temporal.Now.plainDateISO(zone);
do {
date = date.add({ days: 1 });
} while ([6, 7].includes(date.dayOfWeek));
// create ZonedDateTime - will throw if time is invalid; use with 'earlier' or 'later' strategy
const dt = date
.toPlainDateTime({ hour: 9, minute: 0 })
.toZonedDateTime(zone, { disambiguation: 'earlier' });
return dt;
}Pitfalls
- Using Date.UTC or naive Date arithmetic across time zones.
- Not choosing an explicit disambiguation strategy for ambiguous/missing local times.
Reference
- Temporal proposal & polyfill: https://github.com/js-temporal/proposal
5) “Explain circular ES module imports: how live bindings and initialization order work - show a scenario that results in undefined and why.”
Why ask it
- Module semantics are subtle. This shows if the candidate understands live bindings and initialization timing.
What the interviewer looks for
- Correct use of the ECMAScript module spec concepts: binding vs value, hoisting-like initialization order, and time-of-use evaluation.
Example that surprises people
moduleA.js
import { b } from './moduleB.js';
export const a = b + 1;
console.log('a initialized', a);moduleB.js
import { a } from './moduleA.js';
export const b = (typeof a === 'undefined' ? 0 : a) + 1;
console.log('b initialized', b);When you import either module, one of the bindings may be undefined at the time it’s used because module initialization runs in a linked-but-not-fully-initialized state. Live bindings mean that once the exporter sets a value later, importers that read it after initialization will see the final value - but reads during initialization can get undefined.
Pitfalls
- Assuming
importcopies values instead of creating live bindings. - Using exported values during initialization time that depend on other modules’ initialization.
Reference
- ECMAScript module semantics (MDN): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
6) “Describe how V8 optimizes JS (hidden classes, inline caching) and show code that ‘deoptimizes’ a function - then explain how to fix it.”
Why ask it
- Reveals practical performance engineering knowledge and insight into engine behavior.
What the interviewer looks for
- Understanding of hidden classes (shapes) and inline caches (ICs).
- How polymorphic object shapes or changing a property type can cause deopts.
Example: deopt pattern
function addX(o) {
return o.x + 1;
}
addX({ x: 1 });
addX({ x: 2 });
addX({ y: 3, x: 3 }); // different hidden shape
addX({ x: 'str' }); // different type - IC becomes polymorphic or deoptimizesHow to fix
- Keep object shapes consistent (initialize all properties in constructor).
- Avoid toggling types of properties; prefer separate functions for different types.
- Use arrays or typed arrays for numeric-heavy loops.
Pitfalls
- Micro-optimizing without profiling.
Reference
- V8 blog and performance guides: https://v8.dev/blog
7) “You want to sandbox untrusted JS with Proxies and realms. Show an attack that bypasses a naive Proxy sandbox and explain how to harden it.”
Why ask it
- Tests security reasoning, knowledge of JS meta-levels, and practical pitfalls of object capability systems.
What the interviewer looks for
- Awareness of reflective traps and ways proxies can be leaked.
- Mitigations like freezing prototypes, removing direct access to constructors, and running in a separate realm.
Surprising bypass example (high level)
- A naive sandbox proxies global objects but forgets to trap access to Function.prototype.apply or constructor properties. An attacker can obtain a reference to the real Function constructor via some object’s constructor chain or toString coercion and then evaluate arbitrary code.
Hardening strategies
- Run untrusted code in a separate realm (iframe or vm module), not just Proxy the globals.
- Freeze and tamper-proof the intrinsics you expose (Object.freeze on allowed objects, deny access to Function constructor).
- Use SES (Secure ECMAScript) or hardened realms from libraries.
Reference
8) “How do you propagate request-scoped context across async boundaries in Node (e.g. logging request-id through awaits and timers)? Compare AsyncLocalStorage and older continuation-local strategies.”
Why ask it
- Real-world service engineering problem: observability and correlation across async boundaries.
What the interviewer looks for
- Knowledge of AsyncLocalStorage (introduced in Node) vs brittle monkey-patched libraries.
- Understanding of where context can be lost (callbacks from native modules, worker threads).
Answer sketch
- Use AsyncLocalStorage to create a store per incoming request and run the handler inside store.run(). Async hooks under the hood track the async resource lifecycle.
- Explain broken cases: when third-party native modules don’t create proper async resources or use worker threads; also how promises and microtasks are handled.
Pitfalls
- Overuse of global context leading to hard-to-test code.
- Expecting AsyncLocalStorage to work across child processes or across worker boundaries without explicit propagation.
Reference
- Node AsyncLocalStorage docs: https://nodejs.org/api/async_hooks.html#class-asynclocalstorage
9) “Explain WebAssembly interop: passing objects, shared memory with SharedArrayBuffer and Atomics, and structuredClone limits.”
Why ask it
- WebAssembly is increasingly used; passing data safely and efficiently matters for performance and correctness.
What the interviewer looks for
- Recognition that complex JS objects can’t be passed by reference; you must serialize or use SharedArrayBuffer for numeric data.
- Awareness of structuredClone limitations and performance trade-offs.
Answer points
- For numeric arrays, pass an ArrayBuffer/SharedArrayBuffer to Wasm linear memory for zero-copy access.
- To share mutable state across threads, use SharedArrayBuffer + Atomics for synchronization.
- structuredClone can copy many objects but will clone, not share, and certain objects (like functions) are not clonable.
Pitfalls
- Using structuredClone for large data without considering the cost.
- Not synchronizing access to SharedArrayBuffer (race conditions).
Reference
- WebAssembly and SharedArrayBuffer docs: https://developer.mozilla.org/en-US/docs/WebAssembly and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer
10) “Design a lock-free counter shared across WebWorkers using Atomics and SharedArrayBuffer; discuss fairness and starvation.”
Why ask it
- Tests ability to reason about concurrency primitives in JavaScript (rare in interviews but practical for high-performance apps).
What the interviewer looks for
- Correct use of SharedArrayBuffer and Atomics APIs.
- Awareness that Atomics can avoid data races but fairness depends on scheduling.
Answer sketch
- Create a SharedArrayBuffer sized for Int32Array, and use Atomics.add to increment atomically.
Example
// main thread
const sab = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT);
const arr = new Int32Array(sab);
// worker code
Atomics.add(arr, 0, 1); // atomic increment
const v = Atomics.load(arr, 0);Discussion
- Atomics provide atomicity but not starvation-freedom guarantees. Busy-wait loops with Atomics.wait/notify are used for coordination.
- Consider backoff strategies, and avoid spin-waiting on the main thread in browsers.
Reference
- MDN Atomics and SharedArrayBuffer: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics
Final thoughts - what to practice and how to answer under pressure
- Always start answers with a one-sentence summary of your conclusion. Then walk through a mental model (why it works). Finally, give a tiny code or pseudo-code example and the key pitfalls.
- For systems questions, draw trade-offs: correctness vs performance vs complexity.
- When surprised by a corner case, admit it and say what you’d do next (write a small repro, check engine docs, or run a profiler).
If you can explain the rationale behind these ten topics, you won’t just survive tough interviews - you’ll help interviewers see your thinking. That’s the difference between reciting facts and demonstrating engineering judgement.



