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?

Featurevarletconst
ScopeFunctionBlockBlock
HoistedYesYesYes
Initial ValueUndefinedNone (Uninitialized)None (Uninitialized)
Temporal Dead ZoneNoYesYes
Access Before LineAllowedThrows ReferenceErrorThrows 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

FeatureRegular FunctionArrow 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:

  • setTimeout
  • fetch
  • DOM events

are handled outside the Call Stack by the browser or Node.js runtime.


Microtask Queue

Contains:

  • Promise callbacks (.then())
  • await
  • queueMicrotask()

Microtasks always have higher priority than macrotasks.


Macrotask Queue

Contains:

  • setTimeout
  • setInterval
  • setImmediate (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:

  1. console.log(1)
  2. setTimeout is sent to the Web APIs
  3. Promise callback goes to the Microtask Queue
  4. console.log(4)
  5. Call Stack becomes empty
  6. Execute all Microtasks
  7. 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:

  • null
  • undefined

Unlike:

input || "default"

Which also replaces:

  • 0
  • false
  • ""

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.


Test Your Javascript Knowledge