JavaScript interviews test language fundamentals, async patterns, and problem-solving. This guide covers the 50 most common questions — with concise answers and runnable code.
Quick reference
| Topic | Most asked questions |
|---|---|
| Variables | var vs let vs const, hoisting, TDZ |
| Closures | Lexical scope, factory functions, loop bug |
this |
Binding rules, arrow vs regular, call/apply/bind |
| Prototypes | Prototype chain, Object.create, class vs prototype |
| Async | Event loop, Promise, async/await, Promise.all |
| ES6+ | Destructuring, spread, generators, optional chaining |
| Arrays | map/filter/reduce, flat, forEach vs map |
| Error handling | try/catch, custom errors, async error handling |
| Patterns | Debounce, throttle, memoize, curry |
| DOM | Event delegation, bubbling, preventDefault |
Variables and scope
1. What is the difference between var, let, and const?
| Feature | var |
let |
const |
|---|---|---|---|
| Scope | Function | Block | Block |
| Hoisted | Yes (as undefined) |
Yes (TDZ) | Yes (TDZ) |
| Reassignable | Yes | Yes | No |
| Re-declarable | Yes | No | No |
| Global property | Yes (window.x) |
No | No |
function example() {
if (true) {
var a = 1; // accessible outside the if block
let b = 2; // block-scoped
const c = 3; // block-scoped, immutable binding
}
console.log(a); // 1
console.log(b); // ReferenceError
}
Rule: always prefer const; use let when you need reassignment; never use var.
2. What is hoisting?
Hoisting is JavaScript's behaviour of moving declarations to the top of their scope before execution.
console.log(x); // undefined (not ReferenceError)
var x = 5;
// The engine sees it as:
var x;
console.log(x); // undefined
x = 5;
let and const are hoisted but not initialised — accessing them before declaration throws a ReferenceError. The gap between entering scope and the declaration is called the Temporal Dead Zone (TDZ).
console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 10;
3. What is the difference between null and undefined?
let a; // undefined — variable declared but not assigned
let b = null; // null — intentional absence of value
typeof undefined // "undefined"
typeof null // "object" (historical bug in JS)
null == undefined // true (loose equality)
null === undefined // false (strict equality)
4. What is the Temporal Dead Zone (TDZ)?
The TDZ is the region between entering a block scope and the point where the let/const variable is declared. Accessing the variable in this zone throws a ReferenceError.
{
// TDZ starts
console.log(x); // ReferenceError
let x = 5; // TDZ ends here
console.log(x); // 5
}
Closures
5. What is a closure?
A closure is a function that retains access to variables from its outer (enclosing) scope even after the outer function has returned.
function makeCounter() {
let count = 0;
return {
increment() { count++; },
decrement() { count--; },
value() { return count; }
};
}
const counter = makeCounter();
counter.increment();
counter.increment();
console.log(counter.value()); // 2
// count is private — not accessible from outside
6. Classic closure bug: var in a loop
// Bug
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Prints: 3 3 3 — all closures share the same `i`
// Fix 1: use let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0 1 2
}
// Fix 2: IIFE to capture value
for (var i = 0; i < 3; i++) {
((j) => setTimeout(() => console.log(j), 100))(i); // 0 1 2
}
7. What is a factory function?
A factory function uses closures to create objects with private state.
function createUser(name) {
let loginCount = 0; // private
return {
getName: () => name,
login: () => ++loginCount,
getStats:() => ({ name, loginCount })
};
}
const user = createUser('Alice');
user.login();
user.login();
console.log(user.getStats()); // { name: 'Alice', loginCount: 2 }
this keyword
8. What are the four rules for this?
| Rule | Example | this value |
|---|---|---|
| Default binding | fn() |
undefined (strict) / window |
| Implicit binding | obj.fn() |
obj |
| Explicit binding | fn.call(obj) |
obj |
new binding |
new Fn() |
newly created object |
Arrow functions do not have their own this — they inherit from the enclosing lexical scope.
9. How do call, apply, and bind differ?
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const person = { name: 'Alice' };
greet.call(person, 'Hello', '!'); // "Hello, Alice!" — args as list
greet.apply(person, ['Hi', '?']); // "Hi, Alice?" — args as array
const bound = greet.bind(person, 'Hey'); // returns new function
bound('.'); // "Hey, Alice."
10. Why does this lose context in callbacks?
const obj = {
name: 'Alice',
greet() {
setTimeout(function () {
console.log(this.name); // undefined — `this` is the timer context
}, 100);
}
};
// Fix 1: arrow function
greet() {
setTimeout(() => {
console.log(this.name); // 'Alice' — inherits outer `this`
}, 100);
}
// Fix 2: bind
greet() {
setTimeout(function() {
console.log(this.name);
}.bind(this), 100);
}
Prototypes
11. What is the prototype chain?
Every object has a hidden [[Prototype]] link. Property lookups travel up the chain until null is reached.
const animal = { breathes: true };
const dog = Object.create(animal);
dog.barks = true;
console.log(dog.barks); // true (own property)
console.log(dog.breathes);// true (from prototype)
console.log(dog.hasOwnProperty('breathes')); // false
12. What is the difference between __proto__ and prototype?
__proto__— the actual prototype link on every object instance.prototype— a property on constructor functions that becomes the__proto__of instances.
function Dog(name) { this.name = name; }
Dog.prototype.bark = function() { return 'Woof!'; };
const rex = new Dog('Rex');
console.log(rex.__proto__ === Dog.prototype); // true
console.log(rex.bark()); // 'Woof!'
13. How does class relate to prototypes?
class syntax is syntactic sugar over prototype-based inheritance.
class Animal {
constructor(name) { this.name = name; }
speak() { return `${this.name} makes a sound`; }
}
class Dog extends Animal {
speak() { return `${this.name} barks`; }
}
const d = new Dog('Rex');
d.speak(); // "Rex barks"
d instanceof Animal; // true
Object.getPrototypeOf(d) === Dog.prototype; // true
Async JavaScript
14. How does the event loop work?
Call stack → executes synchronous code
Web APIs → handles async (setTimeout, fetch, etc.)
Task queue → macrotasks (setTimeout callbacks)
Microtask queue → Promises, queueMicrotask (higher priority)
Microtasks are drained before the next macrotask.
console.log('1');
setTimeout(() => console.log('2'), 0); // macrotask
Promise.resolve().then(() => console.log('3')); // microtask
console.log('4');
// Output: 1, 4, 3, 2
15. What is the difference between a Promise and async/await?
async/await is syntactic sugar over Promises — it makes async code look synchronous.
// Promise chain
fetch('/api/user')
.then(res => res.json())
.then(user => console.log(user))
.catch(err => console.error(err));
// async/await (equivalent)
async function getUser() {
try {
const res = await fetch('/api/user');
const user = await res.json();
console.log(user);
} catch (err) {
console.error(err);
}
}
16. What are Promise combinators?
| Method | Behaviour |
|---|---|
Promise.all(arr) |
Resolves when all resolve; rejects on first rejection |
Promise.allSettled(arr) |
Always resolves with array of {status, value/reason} |
Promise.race(arr) |
Settles as soon as the first one settles |
Promise.any(arr) |
Resolves on first fulfillment; rejects only if all fail |
const results = await Promise.allSettled([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments')
]);
results.forEach(r => {
if (r.status === 'fulfilled') console.log(r.value);
else console.error(r.reason);
});
17. How do you handle errors in async/await?
// Option 1: try/catch
async function load() {
try {
const data = await fetchData();
return data;
} catch (err) {
console.error(err);
return null;
}
}
// Option 2: .catch() on the awaited promise
async function load() {
const data = await fetchData().catch(err => {
console.error(err);
return null;
});
return data;
}
18. What is the difference between setTimeout(fn, 0) and a resolved Promise?
setTimeout(fn, 0) queues a macrotask. A resolved Promise.then() queues a microtask. Microtasks run before macrotasks, so the Promise callback executes first.
setTimeout(() => console.log('macro'), 0);
Promise.resolve().then(() => console.log('micro'));
// Output: micro, macro
ES6+ features
19. What is destructuring?
Destructuring extracts values from arrays and objects into variables.
// Array
const [first, , third] = [1, 2, 3];
// Object with rename and default
const { name: fullName, age = 0 } = { name: 'Alice' };
// Nested
const { address: { city } } = { address: { city: 'Paris' } };
// In function parameters
function display({ name, score = 100 }) {
return `${name}: ${score}`;
}
20. What is the spread operator vs rest parameters?
// Spread: expand iterable into positions
const a = [1, 2];
const b = [...a, 3, 4]; // [1, 2, 3, 4]
const c = { ...obj, z: 9 }; // shallow copy + override
// Rest: collect remaining args into array
function sum(first, ...rest) {
return rest.reduce((acc, n) => acc + n, first);
}
sum(1, 2, 3, 4); // 10
21. What is optional chaining (?.)?
Safely accesses deeply nested properties — returns undefined instead of throwing.
const user = { profile: { address: null } };
// Without optional chaining
const city = user && user.profile && user.profile.address && user.profile.address.city;
// With optional chaining
const city = user?.profile?.address?.city; // undefined (no error)
// Works on methods and arrays too
user?.getAvatar?.();
arr?.[0]?.name;
22. What are generators?
Functions that can pause (yield) and resume, producing values lazily.
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
const gen = range(1, 3);
gen.next(); // { value: 1, done: false }
gen.next(); // { value: 2, done: false }
gen.next(); // { value: 3, done: false }
gen.next(); // { value: undefined, done: true }
// Useful for infinite sequences
function* naturals() {
let n = 0;
while (true) yield n++;
}
23. What is the difference between for...of and for...in?
const arr = ['a', 'b', 'c'];
// for...of — iterates values of any iterable (array, string, Map, Set)
for (const val of arr) console.log(val); // 'a' 'b' 'c'
// for...in — iterates enumerable property keys (avoid on arrays)
for (const key in arr) console.log(key); // '0' '1' '2'
// Also iterates inherited enumerable properties — use hasOwnProperty guard
Arrays
24. What is the difference between map, filter, and reduce?
const nums = [1, 2, 3, 4, 5];
// map — transforms each element, returns same-length array
nums.map(n => n * 2); // [2, 4, 6, 8, 10]
// filter — keeps elements that pass a test
nums.filter(n => n % 2 === 0); // [2, 4]
// reduce — folds to a single value
nums.reduce((acc, n) => acc + n, 0); // 15
// Chaining
nums
.filter(n => n % 2 !== 0) // [1, 3, 5]
.map(n => n ** 2) // [1, 9, 25]
.reduce((a, b) => a + b); // 35
25. What is the difference between forEach and map?
forEach |
map |
|
|---|---|---|
| Returns | undefined |
New array |
| Side effects | Intended use | Avoid |
| Break early | No | No |
| Chainable | No | Yes |
Use map when you need the transformed array; use forEach for side effects only.
26. How do you flatten a nested array?
const nested = [1, [2, [3, [4]]]];
nested.flat(); // [1, 2, [3, [4]]] — depth 1
nested.flat(2); // [1, 2, 3, [4]]
nested.flat(Infinity); // [1, 2, 3, 4]
// flatMap (map + flat depth 1)
['hello', 'world'].flatMap(w => w.split('')); // ['h','e','l','l','o','w','o','r','l','d']
Functions
27. What is the difference between a function declaration and a function expression?
// Declaration — hoisted (callable before definition)
sayHi(); // works
function sayHi() { return 'hi'; }
// Expression — not hoisted
greet(); // TypeError: greet is not a function
const greet = function() { return 'hello'; };
// Arrow function expression
const add = (a, b) => a + b;
28. What is currying?
Currying transforms a function with multiple arguments into a chain of single-argument functions.
// Normal
const add = (a, b) => a + b;
add(2, 3); // 5
// Curried
const curriedAdd = a => b => a + b;
const add5 = curriedAdd(5);
add5(3); // 8
add5(10); // 15
// Generic curry
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) return fn(...args);
return (...more) => curried(...args, ...more);
};
}
29. What is memoization?
Caching the results of expensive function calls.
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
const fib = memoize(function(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
});
fib(40); // fast, computed once
Objects
30. What is the difference between shallow and deep copy?
const original = { a: 1, nested: { b: 2 } };
// Shallow copy — nested object is still shared
const shallow = { ...original };
shallow.nested.b = 99;
console.log(original.nested.b); // 99 (mutated!)
// Deep copy (modern, built-in)
const deep = structuredClone(original);
deep.nested.b = 99;
console.log(original.nested.b); // 2 (safe)
// JSON trick (doesn't handle Date, undefined, functions)
const deep2 = JSON.parse(JSON.stringify(original));
31. What does Object.freeze do?
Makes an object's properties non-writable and non-configurable (shallow).
const config = Object.freeze({ env: 'production', debug: false });
config.debug = true; // silently fails (error in strict mode)
config.debug; // false
// Only shallow — nested objects are still mutable
const obj = Object.freeze({ nested: { x: 1 } });
obj.nested.x = 99; // works!
32. What is Object.create?
Creates a new object with a specified prototype.
const proto = {
greet() { return `Hello, I'm ${this.name}`; }
};
const alice = Object.create(proto);
alice.name = 'Alice';
alice.greet(); // "Hello, I'm Alice"
// Object.create(null) — no prototype at all (pure hash map)
const map = Object.create(null);
// No .toString, .hasOwnProperty etc.
Error handling
33. What types of errors exist in JavaScript?
| Error type | When thrown |
|---|---|
SyntaxError |
Invalid syntax (parse time) |
ReferenceError |
Accessing undeclared variable |
TypeError |
Wrong type operation (call non-function) |
RangeError |
Number out of valid range |
URIError |
Malformed URI function argument |
EvalError |
Problem with eval() (rare) |
null.foo; // TypeError
undeclaredVar; // ReferenceError
new Array(-1); // RangeError
34. How do you create a custom error?
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'AppError';
this.statusCode = statusCode;
Error.captureStackTrace(this, this.constructor);
}
}
class NotFoundError extends AppError {
constructor(resource) {
super(`${resource} not found`, 404);
this.name = 'NotFoundError';
}
}
try {
throw new NotFoundError('User');
} catch (err) {
if (err instanceof NotFoundError) {
console.log(err.statusCode); // 404
}
}
Common patterns
35. What is debounce? Implement it.
Debounce delays execution until after a pause in calls. Used for search inputs, resize events.
function debounce(fn, delay) {
let timerId;
return function(...args) {
clearTimeout(timerId);
timerId = setTimeout(() => fn.apply(this, args), delay);
};
}
const onSearch = debounce((query) => fetchResults(query), 300);
input.addEventListener('input', e => onSearch(e.target.value));
36. What is throttle? Implement it.
Throttle limits execution to at most once per interval. Used for scroll, mousemove events.
function throttle(fn, limit) {
let inThrottle = false;
return function(...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
const onScroll = throttle(() => updateUI(), 100);
window.addEventListener('scroll', onScroll);
37. What is the difference between debounce and throttle?
| Debounce | Throttle | |
|---|---|---|
| Executes when | After calls stop for delay ms |
At most once per limit ms |
| Use case | Search input, resize end | Scroll, mousemove, resize |
| First call | Delayed | Executed immediately |
DOM
38. What is event delegation?
Attaching a single listener to a parent to handle events from many children — using event bubbling.
// Without delegation (bad — 100 listeners)
document.querySelectorAll('.btn').forEach(btn =>
btn.addEventListener('click', handleClick)
);
// With delegation (good — 1 listener)
document.getElementById('list').addEventListener('click', (e) => {
if (e.target.matches('.btn')) {
handleClick(e.target);
}
});
Delegation also works for dynamically added elements — a major advantage.
39. What is the difference between event.target and event.currentTarget?
event.target— the element that triggered the event (innermost).event.currentTarget— the element the listener is attached to.
document.getElementById('parent').addEventListener('click', (e) => {
console.log(e.target); // clicked child element
console.log(e.currentTarget); // always #parent
});
40. What is event bubbling and capturing?
Events travel in three phases:
- Capture — from
windowdown to the target. - Target — at the target element.
- Bubble — from the target back up to
window.
// Capture phase (third argument = true)
el.addEventListener('click', handler, true);
// Bubble phase (default)
el.addEventListener('click', handler);
// Stop bubbling
el.addEventListener('click', (e) => {
e.stopPropagation();
});
Tricky questions
41. What does typeof null === 'object' mean?
It's a historical bug in JavaScript (the value null was stored with the object type tag 000 in the original engine). It was never fixed to avoid breaking existing code. Use === null to check for null.
42. What is NaN and how do you check for it?
NaN (Not a Number) is the result of invalid numeric operations. It is the only value in JS not equal to itself.
typeof NaN; // "number" (quirk)
NaN === NaN; // false
Number.isNaN(NaN); // true (reliable check)
isNaN('hello'); // true (coerces first — unreliable)
Number.isNaN('hello'); // false (no coercion)
43. What is == vs ===?
== performs type coercion; === requires both value and type to match.
0 == false; // true (coercion)
0 === false; // false
null == undefined; // true
null === undefined; // false
'5' == 5; // true
'5' === 5; // false
Always use === unless you explicitly need coercion.
44. What is the difference between undefined and not defined?
let x;
console.log(x); // undefined — declared, not assigned
console.log(typeof y); // "undefined" — typeof is safe for undeclared vars
console.log(y); // ReferenceError — accessing undeclared variable
45. What is the difference between Array.isArray and instanceof Array?
Array.isArray([]); // true — reliable across iframes
[] instanceof Array; // true — fails across different realms (iframes, vm)
typeof []; // "object" — not useful for arrays
// Use Array.isArray — it works in all environments
46. Explain Symbol
Symbol creates a guaranteed unique value — useful for object keys that won't clash.
const id = Symbol('id');
const user = {
name: 'Alice',
[id]: 42 // not visible in for...in or JSON.stringify
};
user[id]; // 42
user[Symbol('id')]; // undefined — different symbol
47. What is a WeakMap and when do you use it?
WeakMap stores key-value pairs where keys are objects only and are held weakly (won't prevent garbage collection).
const cache = new WeakMap();
function processUser(user) {
if (cache.has(user)) return cache.get(user);
const result = heavyComputation(user);
cache.set(user, result);
return result;
}
// When user object is garbage collected, cache entry is too — no memory leak
48. What is the difference between Map and a plain object {}?
| Feature | Map |
Object {} |
|---|---|---|
| Key types | Any value | String / Symbol |
| Order | Insertion order | Mostly preserved (ES2015+) |
| Size | .size |
Object.keys().length |
| Iteration | for...of |
for...in (includes inherited) |
| Performance | Better for frequent add/delete | Better for static data |
const map = new Map();
map.set({ id: 1 }, 'Alice'); // object as key
map.set(42, 'number key');
map.size; // 2
49. What is a Proxy?
Proxy intercepts and customises operations on objects (get, set, delete, etc.).
const handler = {
get(target, prop) {
return prop in target ? target[prop] : `Property '${prop}' not found`;
},
set(target, prop, value) {
if (typeof value !== 'number') throw new TypeError('Must be a number');
target[prop] = value;
return true;
}
};
const obj = new Proxy({}, handler);
obj.count = 5;
obj.count; // 5
obj.foo; // "Property 'foo' not found"
obj.x = 'a'; // TypeError
50. What is the difference between call stack overflow and maximum call stack size exceeded?
Both refer to stack overflow — too many nested function calls, typically from infinite recursion.
// Causes stack overflow
function recurse() { return recurse(); }
recurse(); // Uncaught RangeError: Maximum call stack size exceeded
// Fix: tail recursion manually (via trampoline) or use iteration
function trampoline(fn) {
return function(...args) {
let result = fn(...args);
while (typeof result === 'function') result = result();
return result;
};
}
Common mistakes
| Mistake | Why it's wrong | Fix |
|---|---|---|
| Mutating state directly | Leads to bugs, breaks React rendering | Use spread or structuredClone |
typeof null === 'object' |
JS quirk — null is not an object | Use === null to check |
[] == false is true |
Type coercion surprises | Use === |
forEach with async |
Awaits don't work inside forEach |
Use for...of instead |
Adding to prototype |
Breaks for...in loops for others |
Use class syntax |
| Not handling Promise rejection | Unhandled rejection crashes Node | Always add .catch() or try/catch |
Using == instead of === |
Type coercion causes bugs | Default to === |
var in loops |
Shared binding in closures | Use let |
FAQ
Q: What's asked most in JavaScript interviews?
A: Closures, the event loop, this binding, promises/async, prototype chain, and common array methods (map/filter/reduce). Know these deeply — they appear in most interviews.
Q: How do I reverse a string in JavaScript?
const rev = str => str.split('').reverse().join('');
For Unicode-aware reversal (emoji, accents): [...str].reverse().join('').
Q: What is the difference between slice and splice?slice(start, end) returns a new array without modifying the original. splice(start, deleteCount, ...items) modifies the original array and returns removed elements.
Q: What does void 0 do?
It evaluates the expression and returns undefined. Used as a safe undefined (before ES5, undefined was reassignable). Prefer undefined directly in modern code.
Q: How do you check if an object is empty?
Object.keys(obj).length === 0; // most common
JSON.stringify(obj) === '{}'; // works but slower
Object.entries(obj).length === 0; // same as keys check
Q: What is the difference between synchronous and asynchronous code?
Synchronous code runs line by line, blocking execution until each operation completes. Asynchronous code (via callbacks, Promises, or async/await) lets the program continue while waiting for I/O operations, handled through the event loop.