JavaScript Output Questions with Answers
"What does this print?" questions from JavaScript output rounds, covering hoisting, closures, this, coercion and the event loop. Every output shown was produced by running the code.
Hoisting and the temporal dead zone
var used before its declaration
function show() {
console.log(score);
var score = 90;
console.log(score);
}
show();undefined
90var score is hoisted to the top of the function and initialised with undefined. Only the assignment = 90 stays where it's written.
let used before its declaration
function show() {
console.log(marks);
let marks = 85;
}
show();ReferenceError: Cannot access 'marks' before initializationlet is hoisted too, but it stays uninitialised in the temporal dead zone until its line runs. Reading it early throws instead of giving undefined.
Closures inside loops
setTimeout in a var loop
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}3
3
3All three callbacks close over the same var i. They run after the loop ends, when i is 3.
setTimeout in a let loop
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}0
1
2let creates a new binding for each iteration, so each callback has its own i.
this in different contexts
A normal method call
const course = {
title: "React",
describe() {
return `Course: ${this.title}`;
},
};
console.log(course.describe());Course: ReactCalled as course.describe(), so this is the object before the dot: course.
A method copied into a variable (strict mode)
"use strict";
const course = {
title: "React",
describe() {
return this.title;
},
};
const describe = course.describe;
try {
console.log(describe());
} catch (error) {
console.log(error.name + ": " + error.message);
}TypeError: Cannot read properties of undefined (reading 'title')describe() is a plain call with nothing before the dot. In strict mode (and in all modules and classes), this is then undefined.
Type coercion surprises
An array plus an object
console.log([] + {});
console.log(String([]), String({}));[object Object]
[object Object]+ with objects converts both to primitives (strings here): [] becomes "" and {} becomes "[object Object]". The empty string plus that is just "[object Object]". The second line starts with an empty string, which is why there's a leading space.
Empty arrays and equality
console.log([] == ![]);
console.log([] == []);
console.log([] == "");true
false
true![] is false (arrays are truthy). Then [] == false converts both sides to numbers: [] becomes "" then 0, and false becomes 0, so 0 == 0. Two separate arrays are different objects, so [] == [] is false.
Promises and setTimeout ordering
The basic order
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D");A
D
C
BSynchronous code runs first (A, D). Then the microtask queue (promise callbacks: C), and only then the next macrotask (the timer: B).
A promise executor runs synchronously
console.log("1");
new Promise((resolve) => {
console.log("2");
resolve();
console.log("3");
}).then(() => console.log("5"));
console.log("4");1
2
3
4
5The function passed to new Promise runs immediately. resolve() doesn't stop it, so 3 still prints. Only the .then callback waits for the microtask queue.
All 100 questions in the ebook
Hoisting and the temporal dead zone
- var used before its declaration
- let used before its declaration
- A function declaration called before it's written
- A function expression called before it's assigned
- An arrow function in a const, called early
- A local var hides a global one
- typeof cannot rescue the temporal dead zone
- Same name for a var and a function
- Two function declarations with the same name
- var inside a block that never runs
- The loop variable after a var loop
- A class used before its declaration
- Default parameters referring to a later parameter
- A let that reads itself in its own initialiser
- Redeclaring a parameter with var
- A named function expression's name
- Reassigning a const versus mutating it
- A function that returns before its helper is defined
- A var and a let with the same name in nested scopes
- Hoisting inside a nested function
Closures inside loops
- setTimeout in a var loop
- setTimeout in a let loop
- Fixing the var loop with an IIFE
- Fixing the var loop with setTimeout's extra arguments
- Fixing the var loop with bind
- An array of functions created in a var loop
- The same array of functions with let
- A let declared outside the loop
- A closure captures the variable, not the value
- Capturing a snapshot instead
- Two counters from the same factory
- forEach callbacks with var outside
- Increasing delays with a var loop
- A while loop with a let inside the body
- for...of with const
- A shared module-level counter
- Simulated click handlers in a var loop
- Closures with an async loop
- A closure keeping a cache alive
- Closures and the loop condition
this in different contexts
- A normal method call
- A method copied into a variable (strict mode)
- A method called through another object
- A regular function nested inside a method
- An arrow function nested inside a method
- An arrow function used as a method
- call and apply with different objects
- Binding a function twice
- call on an arrow function
- new with a bound function
- Extracting a class method
- An arrow function class field
- map with a thisArg
- this inside a getter
- this in a prototype method
- this in a static method
- Borrowing an array method
- A constructor that returns an object
- Method chaining that forgets to return this
- A callback passed to another object's method
Type coercion surprises
- An array plus an object
- Empty arrays and equality
- baNaNa
- Adding null and undefined
- Minus minus
- An array compared with a string
- null compared with 0
- Comparing NaN
- typeof surprises
- Floating point maths
- Comparing strings that look like numbers
- Sorting numbers without a compare function
- parseInt with unexpected input
- Booleans from strings
- Concatenating with null
- Math.max with no arguments
- String times string
- Comparing objects
- Adding numbers and strings in a template
- The comma operator and empty values
Promises and setTimeout ordering
- The basic order
- A promise executor runs synchronously
- A timer inside a then
- A then inside a timer
- Chained then callbacks interleave
- await pauses only its own function
- Two async functions taking turns
- queueMicrotask versus then
- setTimeout delays
- finally passes the value through
- A catch in the middle of a chain
- Several then calls on the same promise
- A non-function passed to then
- Returning a value versus returning a promise
- Promise.race with an already-resolved value
- An error thrown inside a timer
- Async functions and return values
- await inside a loop with a timer
- A thenable object
- Microtasks queued by microtasks