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

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, promise

Pitfalls

  • Assuming setTimeout(…, 0) is «immediate».
  • Not accounting for rendering frames (rAF) in browsers.

References


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 GC

Pitfalls 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


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


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 import copies values instead of creating live bindings.
  • Using exported values during initialization time that depend on other modules’ initialization.

Reference


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 deoptimizes

How 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


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


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


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


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.

Back to Blog

Related Posts

View All Posts »
The Future of JavaScript: Salary Trends for 2025

The Future of JavaScript: Salary Trends for 2025

A data-informed look at how JavaScript developer compensation is likely to change in 2025 - projected salary ranges, the technologies that will push pay higher, geographic effects, and practical steps engineers can take to capture the upside.