JavaScript is one of the most widely used programming languages in the world. Its simple syntax makes it relatively easy to get started, but beneath that simplicity lie many concepts and behaviors that aren't always intuitive.
Topics such as hoisting, closures, the event loop, this, type coercion, the prototype chain, and arrow functions often confuse even experienced developers.
These topics also appear frequently in technical interviews, especially for mid-level and senior positions.
In this article, I've compiled 30 of the most common JavaScript interview questions, along with clear explanations, practical examples, and answers to help you review the most important concepts and prepare for your next technical interview.
Don't forget to take the quiz at the end of the article to test your understanding.
1. What is the difference between var, let, and const?
| Feature | var | let | const |
|---|---|---|---|
| Scope | Function | Block | Block |
| Hoisted | Yes | Yes | Yes |
| Initial Value | Undefined | None (Uninitialized) | None (Uninitialized) |
| Temporal Dead Zone | No | Yes | Yes |
| Access Before Line | Allowed | Throws ReferenceError | Throws ReferenceError |
| Can redeclare | ✅ | ❌ | ❌ |
| Can reassign | ✅ | ✅ | ❌ |
if (true) {
var a = 1;
let b = 2;
const c = 3;
}
console.log(a); // 1
console.log(b); // ReferenceError
console.log(c); // ReferenceError
2. What is Hoisting?
Hoisting is JavaScript's behavior of moving declarations to the top of their scope before execution.
console.log(a);
var a = 5;
Equivalent to:
var a;
console.log(a); // undefined
a = 5;
With let:
console.log(a);
let a = 5;
Throws:
ReferenceError
because of the Temporal Dead Zone (TDZ).
3. What is the Temporal Dead Zone?
The period between entering a scope and declaring a let or const variable.
{
console.log(name);
let name = "John";
}
Output:
ReferenceError
4. Explain this
this depends on how a function is called, not where it is defined (except arrow functions).
Regular method
const person = {
name: "John",
say() {
console.log(this.name);
}
};
person.say();
Output
John
Detached function
const fn = person.say;
fn();
Output (strict mode)
undefined
Arrow function
const person = {
name: "John",
say: () => {
console.log(this.name);
}
};
person.say();
Output
undefined
Arrow functions don't create their own this.
5. Arrow Functions vs Regular Functions
| Feature | Regular Function | Arrow Function |
|---|---|---|
Own this | ✅ Yes | ❌ No (inherits from outer scope) |
Own arguments | ✅ Yes | ❌ No |
Can be used as constructor (new) | ✅ Yes | ❌ No |
Has prototype | ✅ Yes | ❌ No |
| Best for object methods | ✅ Yes | ⚠️ Usually No |
| Best for callbacks | ✅ Yes | ✅ Yes |
Arrow functions inherit this from the surrounding (lexical) scope instead of creating their own.
6. What is a Closure?
A closure is created when an inner function remembers variables from its outer (lexical) scope, even after the outer function has finished executing.
Closures are commonly used for:
- Private variables
- Data encapsulation
- Event handlers
- Callbacks
- React Hooks
function counter() {
let count = 0;
return function () {
count++;
return count;
};
}
const increment = counter();
increment(); // 1
increment(); // 2
increment(); // 3
Even though counter() has already finished executing, the inner function still has access to the count variable because of the closure.
7. Explain the Event Loop
JavaScript is single-threaded, meaning it executes one piece of JavaScript code at a time.
To handle asynchronous operations, JavaScript relies on the Event Loop.
The execution flow is:
Call Stack
↓
Web APIs (Browser / Node.js)
↓
Microtask Queue
↓
Macrotask Queue
Call Stack
Where synchronous code executes.
console.log("A");
console.log("B");
Runs immediately.
Web APIs
Operations like:
setTimeoutfetch- DOM events
are handled outside the Call Stack by the browser or Node.js runtime.
Microtask Queue
Contains:
- Promise callbacks (
.then()) awaitqueueMicrotask()
Microtasks always have higher priority than macrotasks.
Macrotask Queue
Contains:
setTimeoutsetIntervalsetImmediate(Node.js)- MessageChannel
Example:
console.log(1);
setTimeout(() => console.log(2));
Promise.resolve().then(() => console.log(3));
console.log(4);
Output
1
4
3
2
Execution:
console.log(1)setTimeoutis sent to the Web APIs- Promise callback goes to the Microtask Queue
console.log(4)- Call Stack becomes empty
- Execute all Microtasks
- Execute the first Macrotask
This is why Promise callbacks always execute before setTimeout, even if the timeout is 0.
8. What's the difference between == and ===?
== performs type coercion.
1 == "1"
Output
true
=== compares value and type.
1 === "1"
Output
false
9. Explain Type Coercion
Type coercion is the process by which JavaScript automatically converts a value from one type to another when evaluating an expression.
This conversion happens implicitly, and the rules depend on the operator being used.
For example, the subtraction operator (-) expects numbers. If it receives strings that represent numeric values, it automatically converts them to numbers.
"5" - 1
Output:
4
JavaScript converts "5" to the number 5 before performing the subtraction.
The addition operator (+) behaves differently. If one of the operands is a string, it performs string concatenation.
"5" + 1
Output:
"51"
In this case, the number 1 is converted to a string and concatenated with "5".
Another common example:
true + 1
Output:
2
The value true is converted to 1.
Similarly:
false + 1
Output:
1
Because false is converted to 0.
For this reason, it's generally recommended to use === instead of ==, since === compares both value and type without performing automatic type coercion.
10. What are Truthy and Falsy values?
Whenever JavaScript evaluates a condition (if, while, logical operators, etc.), it automatically converts the value to a boolean.
Values that become false are called Falsy.
Everything else is Truthy.
Falsy values:
false
0
-0
0n
""
null
undefined
NaN
Everything else is Truthy.
Examples:
if ("Hello") {
console.log("Runs");
}
Output
Runs
Because non-empty strings are truthy.
if ([]) {
console.log("Runs");
}
Output
Runs
Even an empty array is truthy.
if ({}) {
console.log("Runs");
}
Output
Runs
An empty object is also truthy.
This is a common interview question because many developers expect empty arrays or objects to be falsy.
11. What is the Prototype Chain?
JavaScript uses prototypal inheritance.
When you access a property or method on an object, JavaScript first looks on the object itself.
If it doesn't exist, it looks on the object's prototype.
If it still doesn't find it, it continues walking up the prototype chain until it reaches null.
const obj = {};
obj.toString();
Even though obj doesn't define toString(), JavaScript finds it in:
Object.prototype
The lookup process looks like this:
obj
↓
Object.prototype
↓
null
This mechanism allows objects to inherit methods without copying them into every object.
12. Difference between null and undefined
undefined
let value;
console.log(value);
Output
undefined
null
const user = {
age: null
};
Means: Intentionally empty.
13. Why does typeof null return "object"?
typeof null;
Output
"object"
This is a historical bug kept for backward compatibility.
14. Pass by Value vs Pass by Reference
Primitive values (number, string, boolean, null, undefined, symbol, bigint) are passed by value.
let a = 5;
let b = a;
b = 10;
console.log(a);
Output
5
Changing b doesn't affect a because they store independent values.
Objects and arrays behave differently.
const a = {
x: 1
};
const b = a;
b.x = 2;
console.log(a.x);
Output
2
Both variables point to the same object in memory.
Technically, JavaScript is always pass-by-value.
For objects, the value being copied is the object's reference, so both variables reference the same object.
This distinction is a common senior-level interview question.
15. Can you modify a const object?
Yes.
const person = {
age: 20
};
person.age = 30;
Works.
But:
person = {};
Throws
TypeError
const prevents reassignment, not mutation.
16. Explain Destructuring
Object destructuring:
const person = {
name: "John",
age: 30
};
const { name } = person;
Array destructuring:
const [a, b] = [1, 2];
17. Spread vs Rest Operator
Spread:
const arr = [1, 2];
const copy = [...arr];
Rest:
function sum(...numbers) {
return numbers.reduce((a, b) => a + b);
}
18. Debounce vs Throttle
Debounce
Executes only after the user stops triggering an event.
Common use:
- Search input
- Autocomplete
Throttle
Executes at most once every X milliseconds.
Common use:
- Scroll
- Resize
- Mouse move
19. What is a Promise?
A Promise represents a value that will be available in the future.
States:
- Pending
- Fulfilled
- Rejected
20. Promise vs Async/Await
Promise:
fetchData()
.then(data => console.log(data))
.catch(console.error);
Async/Await:
try {
const data = await fetchData();
} catch (error) {
console.error(error);
}
21. map() vs forEach()
map()
- Returns a new array.
- Used for transformations.
const doubled = numbers.map(n => n * 2);
forEach()
- Returns
undefined. - Used for side effects.
22. filter() vs find()
filter()
Returns all matching elements.
find()
Returns only the first matching element.
23. Shallow Copy vs Deep Copy
Shallow copy:
const copy = {
...obj
};
Nested objects are still shared.
Deep copy:
const copy = structuredClone(obj);
24. Optional Chaining (?.)
user?.address?.city
Avoids:
Cannot read property...
25. Nullish Coalescing (??)
const value = input ?? "default";
Only replaces:
nullundefined
Unlike:
input || "default"
Which also replaces:
0false""
26. What is Lexical Scope?
Variables are resolved based on where functions are defined, not where they are called.
const name = "global";
function outer() {
const name = "local";
function inner() {
console.log(name);
}
return inner;
}
outer()();
Output
local
27. What is an IIFE?
Immediately Invoked Function Expression.
(function () {
console.log("Runs immediately");
})();
Before let and const, IIFEs were commonly used to create private scope.
28. Difference between call, apply, and bind
function greet() {
console.log(this.name);
}
const person = {
name: "Alice"
};
call
greet.call(person);
Invokes immediately.
apply
Math.max.apply(null, [1, 2, 3]);
Invokes immediately using an array of arguments.
bind
const bound = greet.bind(person);
bound();
Returns a new function.
29. Why is [] == false true?
[] == false;
Output
true
Reason:
[] -> ""
"" -> 0
false -> 0
0 == 0
Loose equality performs type coercion.
30. Function Declaration vs Function Expression
Function declaration:
sayHello();
function sayHello() {
console.log("Hello");
}
Works because the function is hoisted.
Function expression:
sayHello();
const sayHello = function () {};
Throws:
ReferenceError
because sayHello is in the Temporal Dead Zone until its declaration executes.