Back to blog
January 31, 2025 5 min read

JavaScript Challenges That Trump the Normal Developer

Tricky JS puzzles that trip up even experienced devs—closures, this, coercion, and the event loop.
JavaScriptCoding ChallengeTricky
Share:
JavaScript Challenges That Trump the Normal Developer

Here are some fun JavaScript challenges that look simple but often stump developers. Each one exploits a quirk of the language—closures, this, type coercion, or the event loop. Try to predict the output (or fix the bug) before peeking at the answers.


Challenge 1: The Closure Loop Trap

What does this log?

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
Answer

3, 3, 3 — not 0, 1, 2.

var is function-scoped, so there’s only one i. By the time the timeouts run, the loop has finished and i is 3. Fix: use let (block-scoped) so each iteration gets its own i, or pass i into an IIFE/setTimeout(..., i).


Challenge 2: this in a Method

What does this log?

const obj = {
  name: "JS",
  greet() {
    console.log(this.name);
  },
};

const greet = obj.greet;
greet();
Answer

undefined (or in sloppy mode in a browser, possibly the global object’s name).

this is determined by how the function is called. greet() is called with no receiver, so this is undefined in strict mode (or the global in sloppy mode). The function wasn’t “taken from” obj in a way that preserves this. Fix: obj.greet() or greet.call(obj).


Challenge 3: Loose Equality Coercion

What does this log?

console.log([] + []);
console.log([] + {});
console.log({} + []);
console.log(true + true + true === 3);
console.log("5" - 3);
console.log(`5${3}`);
Answer
  • [] + []"" (arrays become "" via toString(), so "" + "").
  • [] + {}"[object Object]" (array → "", object → "[object Object]").
  • {} + []0 in some environments: {} can be parsed as an empty block, so +[] is evaluated; +[] coerces to 0.
  • true + true + true === 3true (booleans coerce to numbers: 1 + 1 + 1 === 3).
  • "5" - 32 (- triggers numeric coercion).
  • "5" + 3"53" (+ with a string does string concatenation).

Challenge 4: The Event Loop Order

What’s the log order?

console.log("A");

setTimeout(() => console.log("B"), 0);

Promise.resolve().then(() => console.log("C"));

console.log("D");
Answer

A, D, C, B.

Sync code runs first (A, D). Then the microtask queue (promises) runs before the macrotask queue (setTimeout), so C then B. Many developers guess B before C.


Challenge 5: Object Reference vs “Copy”

What does this log?

const a = { x: 1 };
const b = a;
b.x = 2;
console.log(a.x);
Answer

2.

b and a point to the same object. Mutating b.x mutates the shared object, so a.x is also 2. “Copy” would require something like const b = { ...a } or structuredClone(a).


Challenge 6: Optional Chaining + Nullish

What does this log?

const obj = { a: 0, b: "", c: null, d: undefined };
console.log(obj?.e ?? "missing");
console.log(obj?.a ?? "missing");
console.log(obj?.b ?? "missing");
Answer
  • obj?.e ?? "missing""missing" (e is undefined, so nullish → fallback).
  • obj?.a ?? "missing"0 (?? only falls back for null/undefined; 0 is kept).
  • obj?.b ?? "missing""" (same idea; empty string is not nullish).

Challenge 7: Array Sort “In Place”

What does this log?

const arr = [10, 5, 20, 1];
arr.sort();
console.log(arr);
Answer

[1, 10, 20, 5] — not numeric order.

.sort() with no comparator sorts lexicographically (as strings). So "10" comes before "5". Fix: arr.sort((a, b) => a - b).


Challenge 8: parseInt with a Radix

What does this log?

console.log(Number.parseInt("08"));
console.log(Number.parseInt("08", 10));
console.log(["1", "2", "3"].map(Number.parseInt));
Answer
  • parseInt("08") — historically some engines treated leading 0 as octal; in modern JS it’s usually 8 (treated as decimal). Best practice: always pass radix.
  • parseInt("08", 10)8.
  • ["1", "2", "3"].map(parseInt)[1, NaN, NaN]. map passes (element, index); parseInt takes a second argument as radix. So you get parseInt("1", 0), parseInt("2", 1), parseInt("3", 2) — only the first is valid. Fix: .map((s) => parseInt(s, 10)) or .map(Number).

Challenge 9: Hoisting and Temporal Dead Zone

What does this log?

console.log(foo);
var foo = "bar";

console.log(baz);
let baz = "qux";
Answer
  • First line: undefinedvar foo is hoisted and initialized with undefined; the assignment runs later.
  • Third line: ReferenceErrorlet baz is in the TDZ until the declaration runs, so accessing it throws.

Challenge 10: typeof and Undeclared

What does this log?

console.log(typeof x);
console.log(typeof neverDeclared);
Answer
  • If x was never declared: "undefined"typeof is safe and returns "undefined" for undeclared identifiers (no ReferenceError).
  • Same for neverDeclared: "undefined".

Use Them Wisely

These aren’t meant to gatekeep—they’re a reminder that JavaScript’s “weird parts” (scoping, this, coercion, event loop) can bite you in real code. Use let/const, avoid relying on == coercion, pass radix to parseInt, and remember: microtasks before macrotasks.

If you’ve got a favorite JS gotcha or a different solution, share it.

Thanks for reading!

If you found this helpful, consider sharing it with others.