Chapter 92 โ JS Predict-the-Output
๐ Definition
Predict-the-output questions show a short snippet and ask what it prints. They test whether you understand the engine's real rules โ hoisting, the event loop, this, coercion, references โ rather than whether you can recall definitions. They appear in almost every JavaScript screening round and online test.
Every answer below was verified by running the snippet in Node.js. Cover the answer, predict, then check.
๐ง How to Answer in an Interview
- Read the whole snippet before answering.
- Mark what is synchronous, what is a microtask (
then,awaitcontinuation,queueMicrotask) and what is a macrotask (setTimeout,setImmediate, I/O). - Say your reasoning out loud โ a wrong answer with the right reasoning often scores better than a lucky guess.
Hoisting, scope & closures
1. Hoisting with var and function
console.log(a);
var a = 1;
console.log(foo());
function foo() { return "foo"; }Answer
undefined
foovar a is hoisted and initialised to undefined; the assignment happens later. Function declarations are hoisted completely, body included.
2. TDZ
try {
console.log(b);
let b = 2;
} catch (e) { console.log(e.constructor.name); }Answer
ReferenceErrorlet/const are hoisted but stay uninitialised in the Temporal Dead Zone until their line runs, so reading them early throws ReferenceError.
3. Function expression hoisting
try {
bar();
var bar = function () { console.log("bar"); };
} catch (e) { console.log(e.constructor.name, e.message); }Answer
TypeError bar is not a functionOnly the var bar declaration is hoisted (as undefined), not the function assigned to it. Calling undefined is a TypeError.
4. Shadowing and hoisting inside a function
var x = 10;
function test() {
console.log(x);
var x = 20;
}
test();Answer
undefinedThe local var x is hoisted to the top of test, shadowing the global x. At the console.log it exists but is still undefined.
5. setTimeout in a var loop
for (var i = 0; i < 3; i++) setTimeout(() => console.log(i));Answer
3
3
3var is function-scoped: there is one i shared by all callbacks. By the time the timers fire the loop has finished and i is 3.
6. setTimeout in a let loop
for (let i = 0; i < 3; i++) setTimeout(() => console.log(i));Answer
0
1
2let creates a new binding for each iteration, so each callback closes over its own i.
7. Closures counter
function makeCounter() {
let count = 0;
return { inc: () => ++count, get: () => count };
}
const c1 = makeCounter();
const c2 = makeCounter();
c1.inc(); c1.inc(); c2.inc();
console.log(c1.get(), c2.get());Answer
2 1Each call to makeCounter creates a new scope with its own count; the closures are independent.
8. Closure in IIFE with var
var fns = [];
for (var i = 0; i < 3; i++) {
(function (j) { fns.push(() => j); })(i);
}
console.log(fns.map((fn) => fn()));Answer
[ 0, 1, 2 ]The pre-let fix: the IIFE creates a new scope per iteration, capturing the current value as j.
Event loop & async
9. Event loop order
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
queueMicrotask(() => console.log("4"));
console.log("5");Answer
1
5
3
4
2Synchronous code first (1, 5). Then the whole microtask queue in order (3, 4). Then one macrotask (the timer, 2).
10. async/await order
async function a1() {
console.log("a1 start");
await a2();
console.log("a1 end");
}
async function a2() { console.log("a2"); }
console.log("script start");
setTimeout(() => console.log("timeout"), 0);
a1();
new Promise((resolve) => { console.log("promise executor"); resolve(); })
.then(() => console.log("then"));
console.log("script end");Answer
script start
a1 start
a2
promise executor
script end
a1 end
then
timeouta1 runs synchronously until its first await; a2 also runs synchronously. The rest of a1 is queued as a microtask before the .then, because it was queued first. Timers run last.
11. process.nextTick vs promise vs setImmediate (Node)
setImmediate(() => console.log("immediate"));
setTimeout(() => console.log("timeout"), 0);
Promise.resolve().then(() => console.log("promise"));
process.nextTick(() => console.log("nextTick"));
console.log("sync");Answer
sync
nextTick
promise
timeout
immediateIn Node the nextTick queue drains before the promise microtask queue. Then the event loop starts: timers phase, then check phase (setImmediate). Note: from the main module, timeout vs immediate order can vary by system timing; inside an I/O callback, setImmediate always runs first.
12. Nested microtasks and macrotasks
setTimeout(() => {
console.log("t1");
Promise.resolve().then(() => console.log("p-in-t1"));
}, 0);
setTimeout(() => console.log("t2"), 0);
Promise.resolve().then(() => {
console.log("p1");
setTimeout(() => console.log("t-in-p1"), 0);
});Answer
p1
t1
p-in-t1
t2
t-in-p1Microtask p1 runs first and schedules a new timer at the back of the queue. t1 runs, and its microtask p-in-t1 runs immediately after it โ before the next timer t2. t-in-p1 was queued last.
13. Promise chaining and return values
Promise.resolve(1)
.then((v) => v + 1)
.then((v) => { throw new Error("boom " + v); })
.then(() => console.log("skipped"))
.catch((e) => { console.log(e.message); return 10; })
.then((v) => console.log("after catch", v))
.finally(() => console.log("finally"));Answer
boom 2
after catch 10
finallyA thrown error skips .then handlers until a .catch. A value returned from .catch resumes the chain as fulfilled. finally runs at the end and passes the value through.
14. Promise resolves only once
new Promise((resolve, reject) => {
resolve("first");
resolve("second");
reject(new Error("ignored"));
}).then(console.log);Answer
firstA promise settles once; later resolve/reject calls are ignored.
15. Promise.all fail fast vs allSettled
const ok = (v, ms) => new Promise((r) => setTimeout(() => r(v), ms));
const bad = (ms) => new Promise((_, j) => setTimeout(() => j(new Error("fail")), ms));
Promise.all([ok("a", 10), bad(5)]).catch((e) => console.log("all:", e.message));
Promise.allSettled([ok("a", 10), bad(5)]).then((r) => console.log("allSettled:", r.map((x) => x.status).join(",")));
Promise.race([ok("fast", 1), ok("slow", 20)]).then((v) => console.log("race:", v));
Promise.any([bad(1), ok("any-ok", 5)]).then((v) => console.log("any:", v));Answer
race: fast
all: fail
any: any-ok
allSettled: fulfilled,rejectedrace settles with the first to settle (1 ms). all rejects as soon as any input rejects (5 ms). any resolves with the first fulfilled (5 ms; registered before allSettled). allSettled waits for everything (10 ms) and never rejects.
16. async function returns a promise
async function getNum() { return 42; }
console.log(getNum() instanceof Promise);
getNum().then(console.log);Answer
true
42An async function always returns a promise; return 42 resolves it with 42. The .then callback runs after the synchronous log.
17. await in forEach
const wait = (ms, v) => new Promise((r) => setTimeout(() => r(v), ms));
async function run() {
const out = [];
[3, 1, 2].forEach(async (n) => { out.push(await wait(n * 10, n)); });
console.log("forEach:", out);
const out2 = [];
for (const n of [3, 1, 2]) out2.push(await wait(n * 10, n));
console.log("for...of:", out2);
}
run();Answer
forEach: []
for...of: [ 3, 1, 2 ]forEach ignores the promises returned by async callbacks, so the log runs immediately with an empty array. for...of with await runs sequentially and keeps order.
18. Generators
function* gen() {
const x = yield 1;
console.log("received", x);
yield x * 2;
}
const g = gen();
console.log(g.next().value);
console.log(g.next(5).value);
console.log(g.next().done);Answer
1
received 5
10
trueThe first next() runs to the first yield (1). next(5) resumes and makes the paused yield evaluate to 5, logs, then yields 10. The third next() finishes the function.
this keyword
19. this in object method vs arrow
const obj = {
name: "obj",
regular() { return this.name; },
arrow: () => typeof this,
};
console.log(obj.regular());
console.log(obj.arrow());Answer
obj
objectA method called as obj.regular() gets this = obj. Arrow functions have no own this; they use the enclosing scope's. At the top level of a CommonJS module that is module.exports ({}), in a browser script it is window โ both "object". In an ES module it would be undefined.
20. Lost this
"use strict";
const user = {
name: "Asha",
greet() { return this?.name; },
};
const greet = user.greet;
console.log(greet());
console.log(user.greet());Answer
undefined
AshaAssigning the method to a variable loses the receiver. In strict mode a plain call gets this = undefined (sloppy mode would give the global object).
21. this inside setTimeout callback
const timer = {
name: "timer",
start() {
setTimeout(function () { console.log(this === timer); }, 0);
setTimeout(() => console.log(this === timer), 0);
},
};
timer.start();Answer
false
trueA regular function callback is called by the timer with its own this (a Timeout object in Node, window in browsers). The arrow function keeps this from start(), which is timer.
22. bind cannot be overridden
function show() { return this.v; }
const bound = show.bind({ v: 1 });
console.log(bound.call({ v: 2 }));
console.log(bound.bind({ v: 3 })());Answer
1
1A bound function's this is fixed permanently. Neither call nor a second bind can change it (only new can).
Types, coercion & equality
23. Type coercion basics
console.log(1 + "2");
console.log("5" - 2);
console.log("5" * "2");
console.log(true + 1);
console.log([] + []);
console.log([] + {});
console.log(1 + null);
console.log(1 + undefined);Answer
12
3
10
2
[object Object]
1
NaN+ with a string concatenates. - and * convert to numbers. true โ 1. Arrays/objects convert to strings: [] โ "", {} โ "[object Object]". null โ 0, undefined โ NaN.
24. Equality traps
console.log(null == undefined);
console.log(null === undefined);
console.log(NaN === NaN);
console.log(Object.is(NaN, NaN));
console.log(0 == "");
console.log("0" == false);
console.log([] == false);
console.log([1] == 1);Answer
true
false
false
true
true
true
true
truenull == undefined is a special rule (only equal to each other). NaN is never equal to itself; Object.is handles it. == converts both sides to numbers: "" โ 0, "0" โ 0, false โ 0, [] โ "" โ 0, [1] โ "1" โ 1.
25. typeof quirks
console.log(typeof null);
console.log(typeof []);
console.log(typeof function () {});
console.log(typeof NaN);
console.log(typeof undefinedVariable);
console.log(typeof class {});Answer
object
object
function
number
undefined
functiontypeof null === "object" is a historical bug. Arrays are objects (use Array.isArray). Classes are functions. NaN is a number. typeof an undeclared variable does not throw.
26. Floating point
console.log(0.1 + 0.2 === 0.3);
console.log((0.1 + 0.2).toFixed(2));
console.log(Number.EPSILON > Math.abs(0.1 + 0.2 - 0.3));Answer
false
0.30
trueNumbers are IEEE-754 doubles; 0.1 and 0.2 cannot be represented exactly. Compare with a tolerance (Number.EPSILON) or work in integers (paise, cents).
27. Optional chaining and nullish coalescing
const cfg = { retries: 0, nested: null };
console.log(cfg.retries || 3);
console.log(cfg.retries ?? 3);
console.log(cfg.nested?.value);
console.log(cfg.missing?.());Answer
3
0
undefined
undefined|| replaces any falsy value (including 0). ?? replaces only null/undefined. ?. short-circuits to undefined instead of throwing, including for calls (?.()).
28. Comma and void
console.log((1, 2, 3));
console.log(void 0);Answer
3
undefinedThe comma operator evaluates left to right and returns the last value. void always returns undefined.
29. Infinity and division
console.log(1 / 0, -1 / 0, 0 / 0);
console.log(Math.max(), Math.min());Answer
Infinity -Infinity NaN
-Infinity InfinityDivision by zero gives ยฑInfinity; 0/0 is NaN. Math.max() with no arguments is -Infinity (the identity for max), Math.min() is Infinity.
30. Chained comparison
console.log(1 < 2 < 3);
console.log(3 > 2 > 1);Answer
true
falseEvaluated left to right: 1 < 2 โ true โ true < 3 โ 1 < 3 โ true. 3 > 2 โ true โ true > 1 โ 1 > 1 โ false.
31. Postfix and prefix increment
let counter = 0;
const incr = () => counter++;
incr(); incr();
console.log(counter++, ++counter, counter);Answer
2 4 4After two calls counter is 2. counter++ returns 2 then makes it 3; ++counter makes it 4 and returns 4.
Objects, arrays & prototypes
32. Object references
const p1 = { n: 1 };
const p2 = p1;
p2.n = 2;
console.log(p1.n);
console.log({ a: 1 } === { a: 1 });Answer
2
falseObjects are assigned by reference: p1 and p2 point to the same object. Two separately created objects are never ===, even with identical contents.
33. Object keys are strings
const k = {};
const keyA = { id: 1 };
const keyB = { id: 2 };
k[keyA] = "A";
k[keyB] = "B";
console.log(k[keyA]);
console.log(Object.keys(k));Answer
B
[ '[object Object]' ]Plain-object keys are strings (or symbols). Both objects convert to "[object Object]", so the second write overwrites the first. Use a Map for object keys.
34. Spread is shallow
const orig = { a: 1, nested: { b: 2 } };
const copy = { ...orig };
copy.a = 100;
copy.nested.b = 200;
console.log(orig.a, orig.nested.b);Answer
1 200Spread copies top-level properties only. nested is the same object in both, so changing copy.nested.b changes orig.nested.b.
35. const with objects
const arr = [1, 2];
arr.push(3);
console.log(arr);
try { arr = []; } catch (e) { console.log(e.constructor.name); }Answer
[ 1, 2, 3 ]
TypeErrorconst prevents re-assignment of the variable, not mutation of the object it points to.
36. Object.freeze is shallow
const frozen = Object.freeze({ a: 1, inner: { b: 2 } });
frozen.a = 99;
frozen.inner.b = 99;
console.log(frozen.a, frozen.inner.b);Answer
1 99The top level is frozen (assignment ignored), but inner is a separate, unfrozen object.
37. String immutability
let s = "hello";
s[0] = "H";
console.log(s);
console.log(s.toUpperCase(), s);Answer
hello
HELLO helloStrings are immutable; index assignment is silently ignored (throws in strict mode). Methods return new strings.
38. Array length and holes
const holes = [1, 2, 3];
holes[10] = 11;
console.log(holes.length);
console.log(holes.filter(() => true).length);Answer
11
4Assigning index 10 sets length to 11 and leaves holes (empty slots). Iteration methods like filter skip holes.
39. map with parseInt
console.log(["1", "2", "3"].map(parseInt));Answer
[ 1, NaN, NaN ]map passes (value, index). So the calls are parseInt("1", 0) โ 1 (radix 0 = default), parseInt("2", 1) โ NaN (invalid radix), parseInt("3", 2) โ NaN (3 is not a binary digit). Use .map(Number).
40. sort default is string order
console.log([10, 1, 5, 100].sort());
console.log([10, 1, 5, 100].sort((a, b) => a - b));Answer
[ 1, 10, 100, 5 ]
[ 1, 5, 10, 100 ]Without a comparator, sort converts elements to strings and compares UTF-16 order: "100" < "5". Always pass (a, b) => a - b for numbers. sort also mutates the array.
41. Array destructuring swap and defaults
let m = 1, n = 2;
[m, n] = [n, m];
const [p = 5, q = 6] = [undefined, null];
console.log(m, n, p, q);Answer
2 1 5 nullDestructuring swaps without a temp variable. Defaults apply only for undefined, not null.
42. delete on arrays
const d = [1, 2, 3];
delete d[1];
console.log(d, d.length);Answer
[ 1, <1 empty item>, 3 ] 3delete removes the value but leaves a hole; length is unchanged. Use splice or filter.
43. Default parameters and arguments
function f(a, b = 2) { return arguments.length; }
console.log(f(1));
console.log(f.length);Answer
1
1arguments.length counts what was actually passed (1). f.length counts parameters before the first default (1).
44. Prototype chain
function Animal(name) { this.name = name; }
Animal.prototype.speak = function () { return `${this.name} makes a sound`; };
const dog = new Animal("Rex");
console.log(dog.speak());
console.log(dog.hasOwnProperty("speak"));
console.log(Object.getPrototypeOf(dog) === Animal.prototype);Answer
Rex makes a sound
false
truespeak lives on Animal.prototype, not on dog itself, so hasOwnProperty is false; lookup walks the prototype chain.
45. Class getter and static
class Temp {
static unit = "C";
#c = 25;
get f() { return this.#c * 9 / 5 + 32; }
}
const t = new Temp();
console.log(t.f, Temp.unit, t.unit);Answer
77 C undefined25 * 9 / 5 + 32 = 77. Static fields belong to the class, not instances, so t.unit is undefined.
๐ฏ Likely Follow-Up Questions
- Why does
varin a loop withsetTimeoutprint the same number? Give two fixes. - What is the order of microtasks vs macrotasks? Where does
process.nextTickfit? - How does
awaitchange the order of execution? - What are the rules for
this? How do arrow functions differ? - When does
==behave differently from===? Why is[] == falsetrue? - Why does
["1","2","3"].map(parseInt)not return[1,2,3]? - Why is
0.1 + 0.2 !== 0.3, and how do you handle money in JavaScript?
โ MERN + TS Rapid-Fire Q&A | Index | Next: JS Polyfills & Implementations โ