Core Concepts
These foundational questions appear in virtually every JavaScript interview. Make sure you can explain each concept clearly and back it up with an example.
Q: What is the difference between var, let, and const?
var is function-scoped, hoisted to the top of its scope, and can be re-declared. let is block-scoped, hoisted but not initialized (temporal dead zone), and cannot be re-declared. const is block-scoped like let but must be initialized and cannot be reassigned (though object properties can still be mutated).
var x = 1;
var x = 2; // OK β re-declaration allowed
let y = 1;
// let y = 2; // SyntaxError: already declared
const z = { a: 1 };
z.a = 99; // OK β mutating property
// z = {}; // TypeError: reassignment not allowed
if (true) {
var funcScoped = "I'm accessible outside";
let blockScoped = "I'm NOT accessible outside";
}
console.log(funcScoped); // "I'm accessible outside"
// console.log(blockScoped); // ReferenceError
Q: What is the difference between == and ===?
== (loose equality) performs type coercion before comparing β JavaScript converts operands to a common type. === (strict equality) compares both value and type without any conversion. Always prefer === to avoid unexpected behavior.
console.log(0 == "0"); // true (string coerced to number)
console.log(0 === "0"); // false (different types)
console.log(null == undefined); // true
console.log(null === undefined); // false
console.log(false == 0); // true
console.log(false === 0); // false
Q: What is the difference between null and undefined?
undefined means a variable has been declared but no value has been assigned. null is an intentional assignment representing "no value". typeof undefined returns "undefined" while typeof null returns "object" (a historical bug in JavaScript).
Q: What is hoisting?
Hoisting is JavaScript's default behavior of moving declarations to the top of their scope during the compilation phase. Variable declarations with var are hoisted and initialized to undefined. Function declarations are fully hoisted (both declaration and body). let and const are hoisted but remain in the temporal dead zone until the declaration is reached.
console.log(greeting()); // "Hello!" β function fully hoisted
console.log(name); // undefined β var hoisted, not value
function greeting() { return "Hello!"; }
var name = "Alice";
// let/const in temporal dead zone:
// console.log(city); // ReferenceError
let city = "London";
Q: What is a closure?
A closure is a function that retains access to its outer (enclosing) scope even after the outer function has returned. Closures are used for data privacy, factory functions, and maintaining state in functional patterns.
function makeCounter() {
let count = 0; // private variable
return {
increment() { count++; },
decrement() { count--; },
value() { return count; }
};
}
const counter = makeCounter();
counter.increment();
counter.increment();
counter.increment();
counter.decrement();
console.log(counter.value()); // 2
// count is not accessible from outside
Q: How does the JavaScript event loop work?
JavaScript is single-threaded and uses an event loop to handle asynchronous operations. The call stack executes synchronous code. When async operations complete (timers, I/O, promises), their callbacks are placed in queues. The event loop continuously checks: if the call stack is empty, it first drains the microtask queue (Promise callbacks), then takes one task from the macrotask queue (setTimeout, setInterval) and pushes it onto the stack.
Q: What is the difference between Promises and callbacks?
Callbacks are functions passed as arguments to be called when an async operation completes. They can lead to "callback hell" β deeply nested, hard-to-read code. Promises provide a cleaner, chainable API with .then()/.catch() and avoid nesting. Promises also have better error propagation. async/await (built on Promises) makes async code look synchronous.
Q: How does the this keyword work?
this refers to the object that is executing the current function. Its value depends on how the function is called: in a method, this is the object; in a regular function, this is the global object (or undefined in strict mode); in an arrow function, this is inherited from the enclosing lexical scope. call/apply/bind can explicitly set this.
Q: What is the prototype chain?
Every JavaScript object has an internal [[Prototype]] link to another object (its prototype). When you access a property, JavaScript first looks on the object itself; if not found, it walks up the prototype chain until it reaches null. This is the mechanism behind inheritance in JavaScript.
Q: What is the difference between shallow copy and deep copy?
A shallow copy duplicates the top-level properties only. Nested objects are still shared by reference. A deep copy recursively duplicates all nested objects, creating a fully independent clone.
const original = { a: 1, b: { c: 2 } };
// Shallow copy β nested object still shared
const shallow = { ...original };
shallow.b.c = 99;
console.log(original.b.c); // 99 (mutated!)
// Deep copy β fully independent
const deep = JSON.parse(JSON.stringify(original));
deep.b.c = 42;
console.log(original.b.c); // 99 (unchanged)
// Modern approach (handles more edge cases):
const deepV2 = structuredClone(original);
Object.assign({}, obj) and spread { ...obj }. For deep copy, mention structuredClone() (modern), JSON.parse(JSON.stringify()) (simple but has limitations with functions/dates), and libraries like Lodash _.cloneDeep().
Functions & Scope
Q: What is the difference between arrow functions and regular functions?
Arrow functions: do not have their own this (inherit from enclosing scope), cannot be used as constructors, have no arguments object, and cannot be used as generators. Regular functions have all these capabilities. Use arrow functions for concise callbacks where you want lexical this.
Q: What is an IIFE?
An Immediately Invoked Function Expression (IIFE) is a function that is defined and called immediately. It creates its own scope, preventing variable pollution of the global scope. Common in older code before modules were available.
(function () {
const secret = "I'm scoped inside the IIFE";
console.log(secret);
})();
// Arrow function IIFE:
(() => {
const data = "also private";
console.log(data);
})();
Q: What is currying?
Currying transforms a function with multiple arguments into a sequence of functions each taking a single argument. It enables partial application β creating specialized functions from general ones.
// Normal function
const add = (a, b) => a + b;
// Curried version
const curriedAdd = a => b => a + b;
const add5 = curriedAdd(5); // partial application
console.log(add5(3)); // 8
console.log(add5(10)); // 15
// Practical example: tax calculator
const applyTax = rate => amount => amount + amount * rate;
const applyVAT = applyTax(0.2);
console.log(applyVAT(100)); // 120
Q: What is memoization?
Memoization is an optimization technique that caches the results of expensive function calls so repeated calls with the same arguments return the cached result immediately instead of recomputing.
Q: What is the difference between call, apply, and bind?
All three set this explicitly. call(thisArg, arg1, arg2) invokes the function immediately with arguments listed individually. apply(thisArg, [arg1, arg2]) invokes immediately with arguments as an array. bind(thisArg, arg1) returns a new function with this permanently bound β useful for callbacks.
Q: What is the scope chain?
When JavaScript looks up a variable, it starts in the current scope and walks outward through each enclosing scope until it either finds the variable or reaches the global scope. This ordered lookup path is the scope chain. Closures capture the entire scope chain at the time of function creation.
Q: What are generator functions?
Generator functions (marked with function*) can pause execution and yield values on demand. They return an iterator object. Each call to .next() runs until the next yield and returns { value, done }. Useful for lazy sequences, infinite data streams, and async flow control.
Q: What are higher-order functions?
A higher-order function takes one or more functions as arguments or returns a function. Examples include Array.map(), Array.filter(), Array.reduce(), and any function that accepts a callback.
memoize(fn), which combines closures, higher-order functions, and caching into one clean answer.
Async JavaScript
Q: What is the difference between Promise.all and Promise.allSettled?
Promise.all(promises) resolves when all promises resolve; it rejects immediately if any promise rejects (fail-fast). Promise.allSettled(promises) waits for all promises to settle (resolve or reject) and returns an array of result objects with status and value/reason. Use allSettled when you need results from all operations regardless of failures.
const p1 = Promise.resolve(1);
const p2 = Promise.reject("error");
const p3 = Promise.resolve(3);
// Promise.all β rejects on first failure
Promise.all([p1, p2, p3])
.catch(err => console.log("all failed:", err)); // "all failed: error"
// Promise.allSettled β waits for all
Promise.allSettled([p1, p2, p3]).then(results => {
results.forEach(r => console.log(r.status, r.value ?? r.reason));
});
// fulfilled 1
// rejected error
// fulfilled 3
Q: How do you handle errors in async/await?
Wrap await calls in try/catch blocks. You can also attach .catch() directly to the awaited promise. For multiple async calls, a single try/catch can catch errors from any of them β but be careful with granularity.
Q: What is the difference between microtasks and macrotasks?
Microtasks (Promise callbacks, queueMicrotask, MutationObserver) have higher priority. After each macrotask (setTimeout, setInterval, I/O events), the entire microtask queue is drained before the next macrotask runs. This means Promise callbacks always execute before the next setTimeout callback.
Q: What does setTimeout(fn, 0) do?
It schedules fn as a macrotask to run after the current call stack and all microtasks have been processed. It does not mean "run immediately" β it means "run as soon as possible after the current execution context is free". Minimum delay is typically 4ms in browsers.
Q: How do you handle fetch errors?
fetch() only rejects on network failure β it does NOT reject for HTTP error status codes (4xx, 5xx). You must manually check response.ok or response.status.
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error β status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
if (error.name === "TypeError") {
console.error("Network error:", error.message);
} else {
console.error("Request failed:", error.message);
}
throw error; // re-throw for caller to handle
}
}
Q: What is async iteration?
Async iteration allows you to consume asynchronous data sources sequentially using for await...of. An object is async iterable if it implements Symbol.asyncIterator returning an async iterator. Useful for paginated API responses, streams, and generators.
Q: What is AbortController?
AbortController lets you cancel in-flight fetch requests. Create a controller, pass its signal to fetch, then call controller.abort() to cancel. Useful for cancelling outdated search requests as a user types.
fetch does not throw on 404 or 500 responses. Always check response.ok in production code β interviewers specifically watch for this gap.
OOP & Prototypes
Q: What is the difference between a class and a function constructor?
ES6 class syntax is syntactic sugar over prototype-based inheritance. Classes must be called with new (enforced), are not hoisted like function declarations, and support cleaner syntax for methods, inheritance (extends), and super. Function constructors do the same thing under the hood but are less structured.
Q: How does prototypal inheritance work?
Objects inherit from other objects directly through the prototype chain. Object.create(proto) creates a new object with proto as its prototype. With classes, extends sets up the prototype chain automatically. Methods defined on a class's prototype are shared across all instances.
Q: What are mixins in JavaScript?
Mixins are a pattern for composing multiple behaviors into a class without multiple inheritance. A mixin is an object whose methods are copied onto a class's prototype using Object.assign(Target.prototype, mixin).
Q: What are private class fields?
Private fields (prefixed with #) are truly private to the class β not accessible from outside or from subclasses. They were introduced in ES2022 and are now supported in all modern browsers.
class BankAccount {
#balance = 0; // private field
deposit(amount) {
if (amount > 0) this.#balance += amount;
}
get balance() {
return this.#balance;
}
}
const account = new BankAccount();
account.deposit(500);
console.log(account.balance); // 500
// console.log(account.#balance); // SyntaxError
Q: What is Symbol.iterator?
Symbol.iterator is a well-known symbol used to make any object iterable. When an object has a method at [Symbol.iterator] that returns an iterator (an object with next()), it can be used in for...of loops, spread syntax, and destructuring.
DOM & Browser
Q: What is event delegation?
Event delegation attaches a single event listener on a parent element instead of many listeners on individual children. When a child is clicked, the event bubbles up to the parent listener, which checks the event's target to determine which child triggered it. This is more efficient and works for dynamically added elements.
// Instead of adding a listener to every button:
document.getElementById("button-group").addEventListener("click", (e) => {
if (e.target.matches("button.action-btn")) {
console.log("Clicked:", e.target.dataset.action);
}
});
// Works for buttons added to #button-group after this code runs!
Q: What is the difference between event bubbling and capturing?
When an event fires, it goes through three phases: capturing (travels down from window to target), target (fires on the target element), and bubbling (travels back up). By default, event listeners run in the bubbling phase. Pass true as the third argument to addEventListener to listen in the capturing phase. event.stopPropagation() halts further propagation.
Q: What is the virtual DOM?
The virtual DOM is a lightweight JavaScript object representation of the actual DOM. Libraries like React maintain a virtual tree, diff it against the previous version when state changes, and apply only the minimal real DOM updates needed. This avoids expensive full re-renders.
Q: What is the difference between reflow and repaint?
A reflow (also called layout) recalculates the geometry of elements β their position and size. It's triggered by changes to dimensions, font size, or DOM structure and is expensive. A repaint redraws pixels without recalculating layout β triggered by visual changes like color or visibility. Reflow always triggers repaint; repaint does not trigger reflow. Batch DOM changes and use CSS transforms instead of positional properties to minimize reflows.
Q: What is the difference between debounce and throttle?
Debounce delays execution until a function hasn't been called for a specified period β useful for search-as-you-type where you wait until the user stops typing. Throttle guarantees the function executes at most once per time interval, regardless of how many times it's called β useful for scroll and resize handlers.
Quick-Reference Comparison Table
| Topic | Option A | Option B | Key Difference |
|---|---|---|---|
| Equality | == | === | Type coercion vs strict |
| Scope | var | let/const | Function vs block scope |
| Async | Promise.all | Promise.allSettled | Fail-fast vs wait-all |
| Copy | Shallow | Deep | Top-level vs recursive |
| Event | Debounce | Throttle | Delay-until-idle vs rate-limit |
| Functions | Arrow | Regular | Lexical vs dynamic this |
| Queues | Microtask | Macrotask | Higher vs lower priority |
Interview Preparation Strategy
Knowing the answers is only half the battle. Here are proven strategies for performing well in technical JavaScript interviews.
Practice Exercise
Without looking at any resources, answer these questions aloud as if in an interview:
- Explain the event loop to someone who's never heard of it.
- What happens step by step when you write
await fetch(url)? - Implement a simple
debounce(fn, delay)function from memory. - What does
Object.create(null)create, and when would you use it?
FAQ
How many JavaScript interview questions should I prepare?
Focus on deep understanding of ~50 core questions rather than memorizing 200 surface-level answers. Interviewers probe for understanding, not recitation.
Do I need to know TypeScript for JavaScript interviews?
For many roles (especially front-end/full-stack), TypeScript knowledge is a significant plus. Know the fundamentals: types, interfaces, generics, and type narrowing. Most JavaScript concepts apply directly.
How important are data structures and algorithms in JS interviews?
For FAANG/top-tier companies, DSA is essential. For most startups and mid-size companies, practical JavaScript knowledge and project experience weigh more heavily.
What are the most commonly failed JavaScript interview topics?
The event loop, this binding, closures in loops, and the nuances of async/await error handling are the most commonly stumbled-over topics.
Should I know ES6+ features for interviews?
Absolutely. Destructuring, spread/rest, arrow functions, modules, Promises, async/await, optional chaining (?.), and nullish coalescing (??) are all expected knowledge in 2026.
Interview Challenge
Implement the following three functions from scratch β a common interview exercise:
debounce(fn, delay)β returns a debounced version offnmemoize(fn)β returns a memoized version offndeepEqual(a, b)β returns true if two objects are deeply equal
These three cover closures, higher-order functions, recursion, and object traversal β all in one session.
Summary
- Core concepts:
var/let/const, equality, hoisting, closures, event loop - Functions: arrow vs regular, IIFE, currying, memoization,
call/apply/bind - Async: Promise combinators, async/await error handling, microtask vs macrotask queue
- OOP: class vs constructor, prototypal inheritance, private fields, mixins
- DOM: event delegation, bubbling/capturing, reflow vs repaint, debounce vs throttle
- Always explain your reasoning before writing code β interviewers evaluate your thought process
- Mention edge cases even if you don't handle them:
nullinputs, empty arrays, circular references - Know the time and space complexity of your solutions
- Use modern syntax (
const, arrow functions, destructuring) unless asked otherwise - Be honest when you don't know β "I'm not certain, but I believeβ¦" is better than guessing confidently