JavaScript interviews reuse a small set of questions because a handful of rules explain almost every surprising output: scope and hoisting, how this is bound at call time, the microtask-before-macrotask ordering of the event loop, and coercion under ==.
The value is not in memorising the outputs — it is that each rule collapses a whole family of questions into one sentence. Once you can say 'let creates a fresh binding per iteration,' the loop question, the IIFE follow-up and the closure question all have the same answer.
Trace each snippet before opening the explanation. Every explanation names the follow-up that usually comes next, because the first correct answer only earns you the harder version.
Four areas, five problems
var, let and the closure-in-a-loop classic
1 problemThe honest answer is one word — scope — but the interviewer wants the consequence. var is function-scoped and hoisted (initialised to undefined), let is block-scoped and sits in the temporal dead zone until its declaration runs. That difference is the entire reason the loop-with-setTimeout question has two different answers depending on which keyword you used, and that question is asked in roughly every front-end interview.
How it gets asked: "What is the difference between var and let?" · "Why does this loop log 3, 3, 3?"
Q1
What does each loop log?
for (var i = 0; i < 3; i++) setTimeout(() => console.log(i));
for (let j = 0; j < 3; j++) setTimeout(() => console.log(j));
- A0 1 2 then 0 1 2
- B3 3 3 then 0 1 2
- C0 1 2 then 3 3 3
- D3 3 3 then 3 3 3
▶Show answer & explanation
Answer: B. 3 3 3 then 0 1 2
🐱 var i is one binding shared by all three callbacks; by the time the timers fire the loop has finished and i is 3. let j creates a fresh binding per iteration, so each callback closes over its own value. The follow-up they ask next is how you would fix the var version without changing the keyword — an IIFE per iteration, (function(i){ setTimeout(() => console.log(i)); })(i), which is the pre-ES6 idiom this feature replaced.
this: the four binding rules
1 problemthis is decided at call time by how the function is called, not where it is defined — with one exception that makes the whole topic answerable. In order of precedence: new binds this to the new object; explicit call/apply/bind binds what you pass; a method call binds the object before the dot; otherwise it is undefined in strict mode (or the global object outside it). The exception: arrow functions have no this of their own and inherit it lexically, which is why they fixed the const self = this era.
How it gets asked: "What is this here?" · "Why do arrow functions behave differently?"
Q2
What does this log?
const obj = {
name: 'cat',
regular() { return this.name; },
arrow: () => this?.name,
};
console.log(obj.regular(), obj.arrow());
- A'cat' 'cat'
- B'cat' undefined
- Cundefined 'cat'
- DIt throws a TypeError
▶Show answer & explanation
Answer: B. 'cat' undefined
🐱 regular is called as a method, so this is obj and it returns 'cat'. arrow was defined in the enclosing scope — an object literal creates no scope — so its this is whatever the surrounding scope had (module scope: undefined), giving undefined. The rule to state: never use an arrow function for a method that needs this; do use one for a callback inside a method, which is exactly where the lexical binding helps.
The event loop: why order surprises people
1 problemOne rule answers most of these: synchronous code runs to completion first, then all pending microtasks (promise callbacks, queueMicrotask), then one macrotask (setTimeout, I/O) — and after each macrotask the microtask queue is drained again. So a .then always beats a setTimeout(…, 0) scheduled at the same moment, no matter how the code is ordered on the page.
How it gets asked: "What order does this print?" · "Difference between a microtask and a macrotask?"
Q3
What is the output order?
console.log('A');
setTimeout(() => console.log('B'));
Promise.resolve().then(() => console.log('C'));
console.log('D');
- AA B C D
- BA D C B
- CA D B C
- DA C D B
▶Show answer & explanation
Answer: B. A D C B
🐱 Synchronous first: A, D. Then the microtask queue: C. Then the macrotask: B. The answer A D C B is the canonical demonstration that promises are not just 'faster timers' — they run in a different queue that is drained before any timer gets a turn. The follow-up is usually async/await: an await splits the function and resumes as a microtask, so it behaves like .then for ordering purposes.
== vs === and the coercion questions
1 problem=== compares type and value with no conversion; == converts first, following rules almost nobody memorises correctly. The professional answer is not to recite the coercion table but to state the policy: always use ===, with the single idiomatic exception x == null, which checks null and undefined at once. Interviewers asking [] == false are usually testing whether you know to avoid the situation, not whether you can trace the spec.
How it gets asked: "Difference between == and ===?" · "Why is [] == false true?"
Q4
Which comparison is the one idiomatic use of == in modern JavaScript?
- A
x == 0 to catch empty strings too - B
x == null, which is true for both null and undefined - C
x == false to test falsiness - DNone;
== is deprecated
▶Show answer & explanation
Answer: B. x == null, which is true for both null and undefined
🐱 x == null is the accepted exception because it concisely covers null and undefined and nothing else — many style guides allow exactly this one. The others are traps: '' == 0 is true, [] == false is true, and relying on either produces bugs that survive code review because they look intentional. == is not deprecated, just avoided.