A11 · Part 3 · Advanced
JavaScript interview bank
Thirty-four questions, split by the level they are asked at, with the answer that lands.
05On this page
How to use this
Answer out loud before reading. Recognising an answer and producing one are different skills, and only the second is tested. Where a question links to a chapter, that is where the reasoning lives — this page is recall, not teaching.
Fresher — 0 to 2 years
What is the difference between var, let and const?
var is function-scoped and hoisted as undefined; let and const are block-scoped and sit in the temporal dead zone until declared. const stops reassignment, not mutation — a const object's contents can still change.
What does typeof null return, and why?
"object". A bug from the first implementation, where the type tag for objects was 000 and null was the null pointer — also all zeroes. Kept because fixing it would break the web.
What is the difference between == and ===?
=== compares type and value. == coerces first, with rules worth knowing: null == undefined is true, but null == 0 is false. Use ===; the one common exception is x == null to catch both nullish values.
Which values are falsy?
Exactly eight: false, 0, -0, 0n, "", null, undefined, NaN. Everything else is truthy — including [], {} and "0".
What is hoisting?
Declarations are registered before any code runs. var is initialised to undefined; function declarations are fully available; let, const and class are registered but unreachable until their line — the temporal dead zone.
Difference between null and undefined?
undefined is the language's absence — unassigned variables, missing properties, no return. null is your absence, assigned deliberately to mean "nothing here".
What does this refer to?
How the function was called, not where it was written. Four rules in order: new, then explicit call/apply/bind, then a method call's object, then default (undefined in strict mode). Arrow functions sit outside the rules — they have no this of their own and take the enclosing one. See The this keyword.
Which array methods mutate?
push, pop, shift, unshift, splice, sort, reverse, fill. The rest return a new array. The modern non-mutating twins are toSorted, toReversed, toSpliced and with.
What is the difference between map and forEach?
map returns a new array of the results; forEach returns undefined and exists for side effects. Using map and discarding the result is a signal you meant forEach.
How do you copy an object?
{ ...obj } and Object.assign are shallow — nested objects are shared. For a deep copy, structuredClone is built in and handles Date, Map, Set and cycles. JSON.parse(JSON.stringify(x)) silently drops functions, undefined and dates.
Mid — 2 to 5 years
Explain the event loop. Where do promises and setTimeout sit?
One thread, a call stack, and queues. When the stack empties, all microtasks drain — promise callbacks, queueMicrotask, MutationObserver — before the next macrotask, which is where timers and I/O live. So Promise.resolve().then(...) always logs before setTimeout(..., 0). A promise chain that keeps scheduling can starve timers indefinitely.
What is a closure? Give one from real code.
A function that keeps access to the scope it was created in, after that scope returned. Real ones: a debounce holding its timer, a module keeping private state, a memo cache. The follow-up is always "where have you used one" — have an answer that is not a counter.
What happens with var in a loop with setTimeout?
All callbacks share one binding and log the final value. let creates a fresh binding per iteration and prints 0, 1, 2. The classic pre-ES6 fix was an IIFE to capture the value.
Difference between call, apply and bind?
call invokes with arguments listed, apply with an array, bind returns a new function with this fixed and does not invoke. Partial application falls out of bind.
How does prototypal inheritance work?
Every object has a hidden link to another object. A missed property lookup walks that chain until it finds one or reaches null. class is syntax over the same mechanism — methods live on Constructor.prototype, shared by every instance.
Write debounce.
function debounce(fn, ms) {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn(...args), ms);
};
}
Then say the difference from throttle: debounce fires after the activity stops, throttle fires at most once per interval. Search boxes want debounce; scroll handlers want throttle.
What is event delegation?
One listener on a common ancestor instead of one per child, using e.target to work out what was hit. Fewer listeners, and it keeps working for elements added later.
Promise.all versus allSettled versus race versus any?
all rejects on the first failure; allSettled waits for everything and reports each outcome; race settles on the first to finish either way; any resolves on the first success and rejects only if all fail.
How do you cancel a fetch?
AbortController — pass controller.signal and call abort(). The rejection has name === "AbortError", which you check for rather than reporting as a failure. It is also the fix for the request race where a slow early response overwrites a fast late one.
Deep-equality check, from scratch?
Handle primitives with Object.is, then reject mismatched types, then compare keys by length and recurse. Say the edge cases before you are asked: NaN, -0, arrays versus objects, Date, and cycles.
What is the difference between a Map and an object?
Map takes any key type including objects, preserves insertion order, has a real size, and is iterable. An object coerces keys to strings and carries prototype keys. Use Map when keys are dynamic or not strings.
Explain async/await against promises.
Syntax over the same objects. An async function always returns a promise; await pauses that function and resumes as a microtask. The common mistake is awaiting in a loop where the calls are independent — that is sequential when Promise.all would be parallel.
Senior — 5 years and up
How does the garbage collector decide what to free?
Reachability from roots, not reference counting — which is why cycles are collected. V8 uses a generational collector: a small young space scavenged often, survivors promoted to an old space collected by mark-and-sweep with compaction. See Engine & memory.
What causes a memory leak in a browser app?
Something long-lived holding something large: a listener never removed, an interval never cleared, a subscription never closed, a cache that only grows, a detached DOM node still referenced. The tool is a heap snapshot before and after repeating an action, sorted by retained size.
What are hidden classes and inline caches?
V8 gives objects with the same shape a shared hidden class so property access is an offset rather than a hash lookup, and caches the lookup at each call site. Adding properties in a different order, or later, creates a new shape and can deoptimise the call site. Initialise all fields in the constructor, in one order.
Explain Proxy and Reflect.
Proxy intercepts fundamental operations — get, set, has, deleteProperty — and Reflect gives the default behaviour to delegate to, with the correct receiver. It is how reactive frameworks track dependencies. The cost is real and every trap is a function call.
How do generators work, and what are they for?
function* returns an iterator that pauses at each yield and resumes with a value passed back in. Two-way communication is the point. Uses: lazy or infinite sequences, custom iteration via Symbol.iterator, and cooperative scheduling — async/await is built on the same suspend-and-resume idea.
How would you run heavy work without freezing the page?
Move it to a Web Worker so it runs off the main thread and can be terminated; use SharedArrayBuffer and Atomics only if the data is genuinely large enough to make copying the bottleneck. If it must stay on the main thread, split it into chunks and yield — scheduler.yield or a macrotask — so the browser can paint and handle input.
What is the difference between ESM and CommonJS?
ESM is static — imports are resolved before execution, which makes tree-shaking possible — and its bindings are live views, not copies. CommonJS is dynamic and synchronous, and module.exports is a value copied at require time. The interop pain is almost always default-export shape and top-level await.
How do you prevent XSS in a JavaScript application?
Never build HTML from user input; if you must render HTML, sanitise with a real library. Validate URL protocols before putting them in href — javascript: executes. A Content Security Policy is the seatbelt, not the fix. And anything in a public env variable is public.
Explain the temporal dead zone and why it exists.
Between entering a scope and the let/const declaration, the binding exists but access throws. It exists so that using a value before it is initialised is an error rather than silently undefined — the failure mode var has.
What does 0.1 + 0.2 !== 0.3 tell you, and how do you handle money?
IEEE-754 doubles cannot represent those decimals exactly. Never store money as a float: use integer minor units — paise, cents — or a decimal library, and format for display with Intl.NumberFormat.
How would you implement a concurrency-limited task pool?
Keep a running count, start tasks while it is under the limit, and on each settle start the next from a queue. Say the parts that get skipped: results must stay in input order, one rejection must not strand the pool, and there should be a way to cancel. See Advanced async.
What actually happens between typing a URL and seeing the page?
DNS, TCP, TLS, the HTML response, then parsing — which blocks on synchronous scripts unless they are defer or type="module" — then the CSSOM, layout, paint, and finally your JavaScript running. The point of the question is whether you know where your code sits in that order.
Output-prediction drills
Twenty short "what does this log?" snippets, mixed across topics on purpose — a real interview never asks one at a time. Cover the answer column and say the output out loud before checking; that's the whole exercise.
| Snippet | Logs |
|---|---|
console.log(1); setTimeout(() => console.log(2)); Promise.resolve().then(() => console.log(3)); console.log(4); | 1, 4, 3, 2 |
console.log(typeof typeof 1); | "string" — typeof 1 is "number", and typeof of any string is "string" |
var x = 1; function f() { console.log(x); var x = 2; } f(); | undefined — var x is hoisted inside f, shadowing the outer x before it's assigned |
console.log([1, 2] + [3, 4]); | "1,23,4" — both arrays become strings first, then concatenate |
const fns = []; for (var i = 0; i < 3; i++) fns.push(() => i); console.log(fns.map(f => f())); | [3, 3, 3] — one shared var binding; swap to let for [0, 1, 2] |
console.log(0.1 + 0.2 === 0.3); | false — neither side is exactly representable in binary floating point |
function f() { console.log(this); } f(); | undefined in strict mode/modules, the global object otherwise — a plain call, no object in front of the dot |
console.log([] == false); | true — [] → "" → 0, and false → 0 |
let obj = { a: 1 }; const clone = obj; clone.a = 2; console.log(obj.a); | 2 — clone is the same object, not a copy |
console.log(NaN === NaN); | false — the one value that's never equal to itself; use Object.is or Number.isNaN |
async function f() { return 1; } console.log(f()); | Promise {<fulfilled>: 1} — an async function always returns a promise, even for a plain value |
console.log("5" - 1, "5" + 1); | 4 "51" — - always coerces to numbers, + concatenates the moment either side is a string |
function outer() { let count = 0; return () => ++count; } const inc = outer(); inc(); console.log(inc()); | 2 — one closure, one count, shared across both calls to inc |
console.log(typeof NaN); | "number" — NaN is a special value of the number type, not its own type |
const p = new Promise((res) => res(1)); p.then((v) => console.log(v)); console.log("sync"); | "sync", then 1 — the .then callback is a microtask, queued after all synchronous code finishes |
console.log([1, [2, [3, [4]]]].flat(Infinity)); | [1, 2, 3, 4] — Infinity flattens every level, however deep |
class A { static x = 1; } class B extends A {} console.log(B.x); | 1 — static members are inherited too, not just instance members |
console.log(1 + "1" - 1); | 10 — 1 + "1" is "11" (left to right), then "11" - 1 forces both back to numbers |
let a; console.log(a ?? "default", a || "default"); | "default" "default" — same result here only because undefined is both nullish and falsy; they'd disagree if a were 0 |
console.log(Object.keys({ 2: "b", 1: "a", x: "c" })); | ["1", "2", "x"] — integer-like keys are always visited first, in ascending numeric order, before insertion-order string keys |
Every one of these is explained in full somewhere else on this track — the event loop in One thread, one stack, closures in Closures, coercion in Operators & flow. This table exists because an interview mixes them without warning; reading each chapter once teaches recognition, drilling them mixed like this is what builds recall under pressure.
Dry run: the first drill, traced line by line
| Line | What happens | Queue it lands in |
|---|---|---|
| console.log(1) | runs immediately — synchronous code always runs first | none — logs "1" right now |
| setTimeout(() => console.log(2)) | callback is registered, execution continues past it | macrotask queue |
| Promise.resolve().then(() => console.log(3)) | callback is registered, execution continues past it | microtask queue |
| console.log(4) | runs immediately — still the same synchronous pass | none — logs "4" right now |
| call stack empties | the entire microtask queue drains before anything else runs | console.log(3) fires — logs "3" |
| microtask queue empty | only now does a macrotask get a turn | console.log(2) fires — logs "2" |
Final order: 1, 4, 3, 2 — matching the drills table above.
The rule that produces it is exactly the one the mid-level event-loop
answer already names: all synchronous code first, then the whole
microtask queue, then one macrotask.
The three that decide most interviews
The event loop, closures, and this. They come up at every level,
the follow-ups go deeper each time, and a confident answer to all three buys
you credit for the rest of the hour. Rehearse those out loud even if you skip
everything else here.
Before you move on
- Answer the
thiskeyword question out loud, using the four-rule order from the fresher section. - Trace the first output-prediction drill's queue order yourself, without looking at the answer.
- Explain the difference between
call,apply, andbindwithout looking at the mid-level answer. - Say why
typeof nullis"object", referencing where the bug actually comes from. - Name the three questions that "decide most interviews," and rehearse each one out loud.
Finished reading?
0 of 41 chapters read in this series