25 Challenging JavaScript Questions Every Developer Should Be Ready For
JavaScript is full of quirks, hidden behaviors, and subtle pitfalls that can trip up even experienced developers. In this article, I’ve compiled 25 tricky JavaScript questions that test your understanding of hoisting, scoping, coercion, the event loop, closures, and more. Each question comes with the expected console output and a clear, brief explanation to help you master JavaScript’s nuances. Whether you’re prepping for interviews or just want to sharpen your skills, these brain teasers will deepen your knowledge and challenge your assumptions.
1. Hoisting with var
console.log(a);
var a = 10;
Output: undefined
Explanation:
JavaScript hoists variable declarations (var
) to the top of their scope. However, only the declaration is hoisted, not the assignment. So a
is declared as undefined
at the top, and the 10
is assigned later.
2. Hoisting with let
console.log(b);
let b = 10;
Output: ReferenceError
Explanation:
Variables declared with let
are hoisted too, but they are not initialized. They remain in a “temporal dead zone” from the start of the block until the declaration is evaluated, which makes accessing them before declaration illegal.
3. Function Declaration Hoisting
foo();
function foo() {
console.log("Hello");
}
Output: Hello
Explanation:
Function declarations are hoisted with their full definition. This means foo()
can be called before its declaration because the whole function is moved to the top during compilation.
4. Function Expression Hoisting
bar();
var bar = function () {
console.log("Hi");
};
Output: TypeError: bar is not a function
Explanation:
Although bar
is hoisted as a var
, it is only assigned undefined
during hoisting. The actual function expression isn’t hoisted, so trying to invoke bar()
before the function is assigned results in an error.
5. Arrow Function vs Regular Function (this
)
const obj = {
name: "JS",
regular: function () {
return this.name;
},
arrow: () => {
return this.name;
},
};
console.log(obj.regular()); // 'JS'
console.log(obj.arrow()); // undefined
Explanation:
Regular functions use dynamic this
, so obj.regular()
has this
pointing to obj
. Arrow functions inherit this
from their surrounding scope (in this case, the global scope), which does not have a name
property.
6. setTimeout
with var
in Loop
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
Output: 3 3 3
Explanation:
var
is function-scoped, so the same i
is shared in each iteration. By the time setTimeout
callbacks run, the loop has completed, and i
is 3
.
7. setTimeout
with let
in Loop
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
Output: 0 1 2
Explanation:
let
is block-scoped, so each iteration has its own copy of i
. Each callback gets the correct i
value for that iteration.
8. typeof null
console.log(typeof null);
Output: 'object'
Explanation:
This is a known JavaScript quirk. null
is a primitive, but typeof null
incorrectly returns 'object'
due to legacy reasons in JavaScript's initial implementation.
9. ==
vs ===
console.log(0 == "0"); // true
console.log(0 === "0"); // false
Explanation:
==
performs type coercion, so '0'
is converted to a number. ===
checks both type and value without coercion, so number 0
is not equal to string '0'
.
10. Objects as Keys
const a = {};
const b = {};
a[b] = "hello";
console.log(a[b]);
Output: 'hello'
Explanation:
When using an object as a key, JavaScript converts it to a string — typically "[object Object]"
. So both a[b]
and a["[object Object]"]
refer to the same key.
11. Type Coercion with Arrays and Objects
console.log([] + []); // ""
console.log([] + {}); // "[object Object]"
console.log({} + []); // 0 (or "[object Object]" in some contexts)
Explanation:
Adding arrays and objects triggers toString()
or value coercion. [] + []
is ""
, [] + {}
becomes "" + "[object Object]"
, and {}
can be interpreted as a code block in some contexts, which can lead to confusing results.
12. Event Loop Execution
console.log("start");
setTimeout(() => console.log("timeout"), 0);
Promise.resolve().then(() => console.log("promise"));
console.log("end");
Output:
start
end
promise
timeout
Explanation:
JavaScript executes synchronous code first, then microtasks (promises), and finally macrotasks (like setTimeout
).
13. Variable Shadowing
let x = 5;
function test() {
let x = 10;
console.log(x);
}
test();
console.log(x);
Output:
10
5
Explanation:
The variable x
inside test
shadows the outer x
. Each x
exists in its own scope, so changes in one don't affect the other.
14. Object Reference Behavior
let obj1 = { name: "A" };
let obj2 = obj1;
obj2.name = "B";
console.log(obj1.name);
Output: 'B'
Explanation:
Both obj1
and obj2
reference the same object in memory. Changing a property through one reference reflects on the other.
15. Closure and Persistent Variables
function outer() {
let count = 0;
return function () {
count++;
console.log(count);
};
}
const counter = outer();
counter(); // 1
counter(); // 2
Explanation:
The returned function forms a closure, retaining access to the count
variable even after outer
finishes executing.
16. Implicit Global Variables
(function () {
var x = (y = 5);
})();
console.log(typeof x); // undefined
console.log(typeof y); // number
Explanation:
Here, y = 5
creates a global variable since it's not declared with var
, let
, or const
. x
is block-scoped due to var
.
17. arguments
and Parameter Link
function foo(a, b) {
arguments[0] = 99;
console.log(a);
}
foo(1, 2);
Output: 99
Explanation:
In non-strict mode, function arguments are linked with the arguments
object. Changing arguments[0]
changes a
.
18. Default Destructuring Values
const [a = 1, b = 2] = [undefined, null];
console.log(a, b);
Output: 1 null
Explanation:
a
gets the default because the value is undefined
. b
does not use the default because null
is a valid value.
19. Array Holes and Length
const arr = [1, , 3];
console.log(arr.length); // 3
console.log(arr[1]); // undefined
Explanation:
A missing element in an array creates a “hole” — an index with no value set, though the length still counts it. Accessing it returns undefined
.
20. this
inside setTimeout
const person = {
name: "Alice",
greet: function () {
setTimeout(function () {
console.log(this.name);
}, 1000);
},
};
person.greet();
Output: undefined
Explanation:
Inside setTimeout
, this
refers to the global object, not person
, because it's a regular function. this.name
is therefore undefined
.
21. Immutable Object with Object.freeze
const obj = Object.freeze({ name: "Test" });
obj.name = "Changed";
console.log(obj.name);
Output: 'Test'
Explanation:
Object.freeze
prevents modification of object properties. Attempts to change them fail silently in non-strict mode.
22. Object Destructuring Order
const { a, b } = { b: 1, a: 2 };
console.log(a, b);
Output: 2 1
Explanation:
Destructuring is based on property names, not order. So a
gets the value of a
and b
gets b
.
23. Spread Creates a Copy
const arr = [1, 2, 3];
const copy = [...arr];
copy[0] = 9;
console.log(arr[0]);
Output: 1
Explanation:
Using the spread operator ...
creates a shallow copy. Changing the copy doesn’t affect the original.
24. NaN
Comparison
console.log(NaN === NaN);
Output: false
Explanation:
NaN
is not equal to anything, including itself. This is a unique behavior in JavaScript (and some other languages).
25. typeof
a Function
console.log(typeof function () {});
Output: 'function'
Explanation:
Functions are a special kind of object in JavaScript, and typeof
can uniquely identify them as 'function'
.
Conclusion
JavaScript can often surprise even seasoned developers with its quirks and hidden behaviors. The key to mastering these tricky scenarios isn’t just knowing the right answer, but truly understanding the reasoning behind it. When you carefully read and digest the explanations, patterns begin to emerge — and what once seemed confusing starts to make sense. Whether it’s hoisting, closures, type coercion, or async behavior, developing a deeper grasp of how JavaScript works under the hood will make you a more confident and capable developer. Keep exploring, keep questioning — that’s how real learning happens.
Top comments (0)