JavaScript Interview Questions
30 common JavaScript interview questions — from types and scope to closures, async, and the event loop.
Types & Variables
1. What are the primitive types in JavaScript?
JavaScript has 7 primitive types: string, number, bigint, boolean, undefined, null, and symbol. Primitives are immutable — you cannot change a string in place. Everything else (arrays, objects, functions) is an object type — they are stored by reference.
2. What is the difference between == and ===?
== is loose equality — it performs type coercion before comparing. 0 == '0' is true. === is strict equality — it compares both value and type without coercion. 0 === '0' is false. Always use === in production code to avoid subtle bugs from unexpected coercions.
3. What is the difference between var, let, and const?
var— function-scoped, hoisted with a value ofundefined, can be redeclared. Avoid in modern code.let— block-scoped, hoisted but not initialised (temporal dead zone), cannot be redeclared in the same scope.const— block-scoped likelet, must be initialised at declaration, the binding cannot be reassigned. Note: objects and arrays declared withconstcan still be mutated.
4. What is hoisting?
Hoisting is JavaScript's behaviour of moving declarations to the top of their scope during compilation — before any code runs. var declarations are hoisted and initialised as undefined. Function declarations are hoisted fully (both the name and body). let and const are hoisted but not initialised, causing a ReferenceError if accessed before declaration (the temporal dead zone).
5. What is typeof null and why?
typeof null returns "object" — a well-known bug in JavaScript dating back to the original 1995 implementation. The value null was represented internally with a type tag of 0, which is the same tag used for objects. This bug was never fixed because doing so would break existing code. To check for null, use value === null.
6. What is the difference between null and undefined?
undefined means a variable has been declared but not assigned a value — it is the default. null is an explicit assignment meaning "no value" or "empty" — a developer intentionally set it to nothing. Both are falsy. typeof undefined is "undefined"; typeof null is "object".
Scope & Closures
7. What is scope in JavaScript?
Scope is the context that determines which variables are accessible at a given point in code. JavaScript has: global scope (accessible everywhere), function scope (variables declared inside a function are only accessible within it), and block scope (ES6+: let and const are scoped to the nearest {} block). The scope chain means inner scopes can access outer scope variables, but not vice versa.
8. What is a closure?
A closure is a function that remembers and has access to variables from its outer (enclosing) scope even after that outer function has returned. The inner function "closes over" the variables from the outer scope.
function makeCounter() {
let count = 0; // outer variable
return function() { // inner function — a closure
count++;
return count;
};
}
const counter = makeCounter();
counter(); // 1
counter(); // 2 — count persists!
Closures are used for: data encapsulation, factory functions, memoisation, and event listeners.
9. What is the difference between lexical scope and dynamic scope?
JavaScript uses lexical scope (also called static scope) — a function's scope is determined by where it is written in the source code, not where it is called from. This means closures work predictably regardless of how or when a function is invoked. Dynamic scope (used by some other languages) would look up variables based on the call stack at runtime — JavaScript does not use this model.
10. What is the this keyword?
this refers to the object that is executing the current function. Its value depends on how the function is called:
- In a method:
thisis the object the method belongs to. - In a regular function:
thisisundefinedin strict mode,windowin sloppy mode. - In an arrow function:
thisis inherited lexically from the surrounding scope. - With
new:thisis the newly created object. - With
call/apply/bind:thisis explicitly set.
Functions
11. What is the difference between function declarations and function expressions?
A function declaration (function foo() {}) is hoisted — it can be called before it appears in the code. A function expression (const foo = function() {}) is not hoisted — the variable is hoisted but not initialised, so calling it before declaration throws a TypeError. Arrow functions are always expressions.
12. What is an IIFE?
An Immediately Invoked Function Expression — a function that runs immediately after it is defined. Used to create a private scope and avoid polluting the global scope. In modern JavaScript, modules and block-scoped variables have mostly replaced IIFEs.
(function() {
const private = 'only accessible here';
// code here
})();
13. What are higher-order functions?
Functions that take other functions as arguments or return functions. Examples: Array.map(), Array.filter(), Array.reduce(), setTimeout(). They enable a functional programming style and are fundamental to JavaScript development.
14. What is the difference between call, apply, and bind?
All three set the this value of a function explicitly.
fn.call(thisArg, arg1, arg2)— calls the function immediately with individual arguments.fn.apply(thisArg, [arg1, arg2])— calls the function immediately with arguments as an array.fn.bind(thisArg)— returns a new function withthispermanently bound; does not call it.
Arrays & Objects
15. What is the difference between map, filter, and reduce?
map(fn)— transforms every element, always returns a new array of the same length.filter(fn)— keeps only elements where the callback returns truthy, returns a new (possibly shorter) array.reduce(fn, initial)— collapses the array to a single value (sum, object, another array, etc.) by accumulating a result.
16. What is the difference between shallow copy and deep copy?
A shallow copy copies the top-level properties of an object. Nested objects are still shared (the copy holds a reference to the same nested object). Methods: { ...obj }, Object.assign({}, obj), [...arr]. A deep copy recursively copies all nested objects. Methods: JSON.parse(JSON.stringify(obj)) (simple but loses functions/undefined/Date), or structuredClone(obj) (native, modern, more complete).
17. What is destructuring?
Destructuring lets you unpack values from arrays or objects into variables in a single statement:
const [first, second] = [1, 2, 3];
const { name, age = 30 } = { name: 'Alice' };
const { address: { city } } = user; // nested
18. What does the spread operator do?
The spread operator (...) expands an iterable (array, string, object) into individual elements. It is used for merging arrays/objects, copying, passing array items as function arguments, and converting iterables to arrays. const merged = { ...obj1, ...obj2 } creates a shallow merge.
Async JavaScript
19. What is the event loop?
JavaScript is single-threaded — it can only run one thing at a time. The event loop enables asynchronous behaviour. The call stack executes synchronous code. Asynchronous callbacks (from setTimeout, fetch, DOM events) go to a queue. When the call stack is empty, the event loop moves the next callback from the queue to the stack. Microtasks (Promises) have a higher-priority queue and run before macrotasks (setTimeout, setInterval) after each task.
20. What is the difference between a callback, a Promise, and async/await?
All three handle asynchronous operations. Callbacks are passed as arguments and called when the async operation completes — can lead to deeply nested "callback hell". Promises represent a future value, chainable with .then()/.catch() — flat, more readable. async/await is syntactic sugar over Promises, making async code look and read like synchronous code — the most readable option.
21. What happens if you forget await before a Promise?
Without await, you get the Promise object itself rather than its resolved value. The code continues executing synchronously without waiting for the result. This is a common bug: const data = fetch(url) gives you a Promise, not the data — you need const data = await fetch(url).
22. What is Promise.all and when should you use it?
Promise.all([p1, p2, p3]) waits for all promises to resolve and returns an array of results. It runs the promises in parallel — significantly faster than awaiting them sequentially. It rejects immediately if any promise rejects. Use it when you have multiple independent async operations that should all complete before continuing.
23. Why is async in a forEach callback a problem?
forEach does not await async callbacks — it fires them all and moves on immediately. The loop finishes before any of the promises resolve. Use a for...of loop with await for sequential async operations, or Promise.all(arr.map(async item => ...)) for parallel.
DOM & Browser
24. What is event bubbling?
When an event fires on an element, it first runs that element's listeners, then bubbles up through each ancestor in the DOM tree, triggering their listeners for the same event type. This continues until it reaches the document. event.stopPropagation() prevents further bubbling.
25. What is event delegation?
Instead of attaching listeners to each child element, one listener is attached to a parent. Events from children bubble up to the parent. Use event.target.closest('.selector') to identify which child was clicked. Benefits: works for dynamically added elements, fewer listeners = better performance.
26. What is the difference between innerHTML and textContent?
innerHTML parses the assigned string as HTML — any tags inside it become actual DOM elements. This enables XSS attacks if user-supplied data is assigned. textContent treats the value as plain text — any HTML characters are escaped and displayed literally. Always use textContent for user data.
27. What is localStorage and how does it differ from sessionStorage?
Both are browser APIs for key/value string storage. localStorage persists until explicitly cleared — survives browser restarts and closing tabs. sessionStorage is cleared when the browser tab is closed. Both are limited to ~5MB, synchronous, same-origin only, and should not store sensitive data.
Common "Gotcha" Questions
28. What does 0.1 + 0.2 === 0.3 evaluate to?
false. Floating-point arithmetic in JavaScript (and most languages using IEEE 754) has rounding errors. 0.1 + 0.2 equals 0.30000000000000004. To compare floating-point values, check that the difference is smaller than an acceptable epsilon: Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON.
29. What is NaN and how do you check for it?
NaN stands for "Not a Number" — it is returned by invalid arithmetic operations (e.g. 'hello' * 2). Its type is 'number' (confusingly). The only value in JavaScript not equal to itself: NaN === NaN is false. Check with Number.isNaN(value) (not the global isNaN(), which coerces first).
30. What is the difference between for...in and for...of?
for...in iterates over the enumerable property names (keys) of an object — including inherited properties. Avoid using it on arrays. for...of iterates over the values of any iterable (arrays, strings, Maps, Sets). It does not iterate over plain object keys. Use Object.entries() + for...of to iterate over an object's key/value pairs.