"Guess the Output" JS Trivia Sprint
Test your JavaScript type coercion, hoisting, and scope knowledge in a 60s sprint.
Ready for the JS Trivia Sprint?
You have 60 seconds to predict what JavaScript snippets log or evaluate to. A single wrong answer breaks your streak and ends the sprint!
Mastering JavaScript Weirdness, Type Coercion & Quirks
JavaScript is celebrated for its flexibility, but infamous for counter-intuitive evaluation rules resulting from dynamic typing, implicit type coercion, and historical ECMAScript specifications. Understanding these nuances is essential for writing resilient code and acing technical frontend interviews.
1. Implicit type coercion and the ToPrimitive abstract operation
Whenever JavaScript encounters mismatched operand types in binary expressions, the engine invokes internal abstract operations specified by ECMAScript. The most critical is ToPrimitive(input [, PreferredType]):
If you ever need to inspect or validate complex JSON data structures without unexpected coercion side effects, use our JSON Formatter.
2. The JavaScript event loop: call stack, microtasks & macrotasks
JavaScript is single-threaded, coordinating asynchronous operations using an event loop with two distinct task queues:
- Microtask Queue: Handles
Promise.then(),catch(),finally(),queueMicrotask(), andMutationObserver. Microtasks execute immediately after the current synchronous script runs, before rendering or yielding to macrotasks. - Macrotask Queue (Task Queue): Handles
setTimeout(),setInterval(),setImmediate(), and user UI events. A new macrotask only executes after all pending microtasks have completely drained.
console.log("1");
setTimeout(() => console.log("2"), 0); // Macrotask
Promise.resolve().then(() => console.log("3")); // Microtask
console.log("4");
// Output sequence: "1", "4", "3", "2"3. Variable hoisting, lexical scope & the Temporal Dead Zone (TDZ)
During the compile phase before code execution, JavaScript scans for variable and function declarations:
varis hoisted to the top of its enclosing function scope and initialized withundefined. Referencing it before its line executes returnsundefinedwithout throwing an error.letandconstare hoisted to the top of their block scope, but remain uninitialized in the Temporal Dead Zone (TDZ). Accessing them before initialization throws a fatalReferenceError.- Function Declarations vs Expressions: Function declarations (
function foo() {}) are fully hoisted along with their function body. Function expressions assigned to variables (const foo = () => {}) remain in the TDZ.
4. Prototypal inheritance and 'this' binding mechanics
In JavaScript, the value of this is determined at invocation time by how a function is called:
Default & Implicit Binding
A standalone function call sets this to the global object (or undefined in strict mode). When invoked as obj.method(), this implicitly binds to obj.
Arrow Functions & Lexical 'this'
Arrow functions do not bind their own this, arguments, or super. They permanently inherit this from the enclosing lexical execution context.
5. IEEE 754 binary floating-point numbers & NaN gotchas
JavaScript numbers are 64-bit binary floating-point values (double precision). Because computers store numbers in base-2 binary rather than base-10 decimal, fractions like 0.1 (1/10) cannot be represented cleanly, producing repeating binary decimals. As a result, 0.1 + 0.2 === 0.30000000000000004, making 0.1 + 0.2 === 0.3 evaluate to false. To safely compare floating-point numbers in production, check if the difference is smaller than Number.EPSILON.
6. Explore more developer practice games & utilities
Recreate modern CSS shapes and gradients under the timer in our CSS Speed Challenge, test your input latency on the Dev Reaction Time Test, or benchmark your coding velocity on our Speed Typing Test.
Frequently asked questions
Why does [] + [] evaluate to an empty string?
Binary + coerces arrays to primitives via the internal ToPrimitive operation. Converting an empty array [] yields an empty string "", resulting in "" + "" = "".
Why does [] == ![] evaluate to true?
The logical NOT operator (!) runs first, turning [] into boolean false. Loose equality (==) coerces boolean false to 0, and the empty array [] to "" then 0, leading to 0 == 0, which is true.
Why is typeof NaN equal to 'number'?
NaN stands for "Not-a-Number", but the IEEE 754 floating-point specification defines it as a numeric value representing an undefined calculation (such as Math.sqrt(-1)).
How does the microtask queue differ from the macrotask queue?
Microtasks (Promise callbacks, queueMicrotask) have strict priority over macrotasks (setTimeout, setInterval). The event loop empties all queued microtasks before proceeding to the next macrotask.
What is the Temporal Dead Zone (TDZ)?
The TDZ is the state between entering a block scope and the line where a let or const variable is declared. Accessing the variable in the TDZ triggers an immediate ReferenceError.
How does 'this' binding differ in arrow functions?
Regular functions bind 'this' dynamically at call time based on the invoking context. Arrow functions do not have their own 'this'; they inherit 'this' statically from their outer lexical scope.