These are the “What’s the output?” questions that come up in almost every JavaScript interview. For each one, try to guess the answer before reading the explanation.
1. var hoisting in loops with setTimeout
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
Output: 3, 3, 3
Why: var is function-scoped, not block-scoped. There’s only one i variable shared across all iterations. By the time the setTimeout callbacks run, the loop has already finished and i is 3. If we change var to let, each iteration gets its own i and we’d get 0, 1, 2.
2. == type coercion traps
console.log([] == false);
console.log([] == ![]);
console.log('' == false);
console.log(0 == '');
Output:
true
true
true
true
Why: The == operator coerces both sides to the same type. [] == false — the array is coerced to "", then to 0, and false is 0, so 0 == 0 is true. [] == ![] — ![] is false (arrays are truthy), then it becomes [] == false, which we just covered. '' == false — both coerce to 0. 0 == '' — the empty string coerces to 0. This is exactly why we use === instead of ==.
3. this in different contexts
const obj = {
name: 'Manish',
greet: function() { console.log(this.name); },
greetArrow: () => { console.log(this.name); }
};
obj.greet();
obj.greetArrow();
const fn = obj.greet;
fn();
Output:
Manish
undefined
undefined
Why: obj.greet() — regular function called on obj, so this is obj. obj.greetArrow() — arrow functions don’t have their own this, they use the this from the surrounding scope (which is the module/global scope, where name is undefined). fn() — we extracted the function and called it without any object, so this is the global object (or undefined in strict mode), and this.name is undefined.
4. Promise and setTimeout ordering
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
Promise.resolve().then(() => {
console.log('D');
setTimeout(() => console.log('E'), 0);
});
console.log('F');
Output: A, F, C, D, B, E
Why: Synchronous code runs first (A, F). Then the microtask queue is drained — Promise callbacks C and D run (microtasks have higher priority). While running D, a new setTimeout E is scheduled. Now the macrotask queue runs: B (scheduled first) then E. The rule is: all microtasks drain before the next macrotask.
5. typeof gotchas
console.log(typeof null);
console.log(typeof undefined);
console.log(typeof NaN);
console.log(typeof typeof 1);
Output:
object
undefined
number
string
Why: typeof null is "object" — this is a famous bug from the first version of JavaScript that was never fixed. typeof undefined is "undefined". typeof NaN is "number" — yes, “Not a Number” is technically a number type. typeof typeof 1 — inner typeof 1 returns the string "number", then typeof "number" returns "string".
6. Array method surprises
console.log([1, 2, 3].map(parseInt));
Output: [1, NaN, NaN]
Why: map passes three arguments to the callback: (element, index, array). So it actually calls parseInt(1, 0), parseInt(2, 1), parseInt(3, 2). parseInt(1, 0) — radix 0 is treated as radix 10, returns 1. parseInt(2, 1) — radix 1 is invalid (base-1 doesn’t exist), returns NaN. parseInt(3, 2) — radix 2 means binary, and 3 is not valid binary, returns NaN.
7. Object reference and comparison
const a = { x: 1 };
const b = { x: 1 };
const c = a;
console.log(a === b);
console.log(a === c);
console.log({ x: 1 } === { x: 1 });
Output:
false
true
false
Why: Objects are compared by reference, not by value. a and b look the same but they’re two different objects in memory — different references. c was assigned the same reference as a, so a === c is true. The last one creates two brand new objects — different references, so false.
8. Closure and setTimeout with let vs var
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
for (var j = 0; j < 3; j++) {
(function(j) {
setTimeout(() => console.log(j), 0);
})(j);
}
Output: 0, 1, 2, 0, 1, 2
Why: First loop — let creates a new binding for each iteration, so each callback captures its own i. Second loop — the IIFE creates a new scope for each iteration, capturing the current value of j as a parameter. Both are fixes for the classic var + setTimeout problem. The IIFE approach is the pre-ES6 solution, let is the modern one.