Top 50 JavaScript Interview Questions and Answers in 2026
Top 50 JavaScript Interview Questions and Answers in 2026
Ace your JavaScript interview with these 50 essential questions and detailed answers covering ES2026, async/await, closures, React, Node.js, and system design.
Prepare for Your JavaScript Interview in 2026
JavaScript remains one of the most in-demand programming languages, and technical interviews are more rigorous than ever. This guide covers the 50 most frequently asked JavaScript interview questions in 2026, with detailed answers that reflect current best practices and modern JavaScript features.
Whether you are applying for a junior role or a senior engineering position, these questions are organized by difficulty and topic area to help you focus your preparation.
Core JavaScript Fundamentals
1. What is the difference between var, let, and const?
var has function scope (or global scope if declared outside a function) and is hoisted to the top of its scope with an initial value of undefined.
let has block scope and is hoisted but not initialized (temporal dead zone). It can be reassigned.
const has block scope, is hoisted but not initialized, and cannot be reassigned. Note: const does not make objects immutable β you can still mutate object properties.
var x = 1;
let y = 2;
const z = 3;
function example() {
console.log(a); // undefined (hoisted)
var a = 5;
// console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 6;
}
const obj = { value: 1 };
obj.value = 2; // This is allowed
// obj = {}; // This throws an error
Rule of thumb: Use const by default, let when reassignment is needed, and never use var in modern code.
2. Explain closures with a practical example.
A closure is a function that retains access to variables from its outer scope, even after the outer function has returned.
function createCounter(initialValue = 0) {
let count = initialValue;
return {
increment() { return ++count; },
decrement() { return --count; },
getCount() { return count; }
};
}
const counter = createCounter(10);
console.log(counter.increment()); // 11
console.log(counter.increment()); // 12
console.log(counter.decrement()); // 11
console.log(counter.getCount()); // 11
// count variable is not accessible from outside
Closures are commonly used for:
- Data privacy and encapsulation
- Function factories
- Memoization
- Maintaining state in callbacks
3. What is event delegation and why use it?
Event delegation is attaching a single event listener to a parent element instead of multiple listeners to child elements. Events bubble up the DOM, so the parent catches events from its children.
// Without delegation (bad for dynamic lists)
document.querySelectorAll('.item').forEach(item => {
item.addEventListener('click', handleClick);
});
// With delegation (works for current and future elements)
document.querySelector('.list').addEventListener('click', (event) => {
if (event.target.classList.contains('item')) {
handleClick(event.target);
}
});
Benefits: Better performance for many elements, works for dynamically added elements, simpler memory management.
4. Explain the JavaScript event loop.
JavaScript is single-threaded but handles asynchronous operations through the event loop.
The components:
- Call Stack: Where synchronous code executes
- Web APIs: Browser features (setTimeout, fetch, DOM events) that run outside the stack
- Callback Queue (Task Queue): Callbacks from Web APIs waiting to be executed
- Microtask Queue: Promises and MutationObserver callbacks (higher priority than callback queue)
- Event Loop: Continuously checks if the call stack is empty, then moves tasks from the queues
console.log('1 - Start');
setTimeout(() => console.log('4 - setTimeout'), 0);
Promise.resolve()
.then(() => console.log('3 - Promise microtask'));
console.log('2 - End');
// Output: 1, 2, 3, 4
// Microtasks (Promise) always run before macrotasks (setTimeout)
5. What is the difference between == and ===?
== performs type coercion before comparison. === compares both value and type without coercion.
0 == false // true (false is coerced to 0)
0 === false // false (different types)
'' == false // true (both coerce to 0)
null == undefined // true (special case)
null === undefined // false
// Always use === unless you specifically need type coercion
Asynchronous JavaScript
6. Explain Promises and the three states.
A Promise represents the eventual result of an asynchronous operation. It has three states:
- Pending: Initial state, neither fulfilled nor rejected
- Fulfilled: Operation completed successfully, has a value
- Rejected: Operation failed, has a reason (error)
const fetchUser = (id) => new Promise((resolve, reject) => {
if (id <= 0) {
reject(new Error('Invalid ID'));
return;
}
setTimeout(() => resolve({ id, name: 'Alice' }), 100);
});
fetchUser(1)
.then(user => console.log(user))
.catch(error => console.error(error))
.finally(() => console.log('Done'));
7. What is async/await and how does it relate to Promises?
async/await is syntactic sugar over Promises that makes asynchronous code look and behave more like synchronous code.
// Promise chain
function getUserData(userId) {
return fetchUser(userId)
.then(user => fetchUserPosts(user.id))
.then(posts => ({ user, posts }))
.catch(error => { throw new Error(`Failed: ${error.message}`); });
}
// Async/await equivalent
async function getUserData(userId) {
try {
const user = await fetchUser(userId);
const posts = await fetchUserPosts(user.id);
return { user, posts };
} catch (error) {
throw new Error(`Failed: ${error.message}`);
}
}
An async function always returns a Promise. await pauses execution of the async function until the Promise resolves.
8. How do you handle multiple Promises concurrently?
// Sequential (slow - waits for each one)
const user = await fetchUser(id);
const profile = await fetchProfile(id); // Waits for user first
// Concurrent (fast - runs in parallel)
const [user, profile] = await Promise.all([
fetchUser(id),
fetchProfile(id)
]);
// Promise.allSettled - waits for all, includes failures
const results = await Promise.allSettled([
fetchUser(id),
fetchProfile(id),
fetchSettings(id)
]);
results.forEach(result => {
if (result.status === 'fulfilled') console.log(result.value);
else console.error(result.reason);
});
// Promise.race - resolves/rejects with first to settle
const result = await Promise.race([
fetch('/api/primary'),
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 5000))
]);
9. What is the difference between Promise.all and Promise.allSettled?
Promise.all rejects immediately if any Promise rejects. Promise.allSettled waits for all Promises and returns results for each, whether fulfilled or rejected. Use allSettled when you need results from all operations regardless of failures.
Object-Oriented JavaScript
10. Explain prototypal inheritance.
Every JavaScript object has an internal link to another object called its prototype. When you access a property, JavaScript first looks at the object, then its prototype, then the prototype's prototype, forming the prototype chain.
const animal = {
speak() {
return `${this.name} makes a sound`;
}
};
const dog = Object.create(animal);
dog.name = 'Rex';
dog.bark = function() { return `${this.name} barks`; };
console.log(dog.speak()); // "Rex makes a sound" (from prototype)
console.log(dog.bark()); // "Rex barks" (own property)
11. How do ES6 classes differ from prototype-based patterns?
ES6 classes are syntactic sugar over prototypal inheritance. Under the hood, they still use prototypes.
class Animal {
#name; // Private field (ES2022)
constructor(name) {
this.#name = name;
}
get name() { return this.#name; }
speak() {
return `${this.#name} makes a sound`;
}
static create(name) {
return new Animal(name);
}
}
class Dog extends Animal {
#breed;
constructor(name, breed) {
super(name);
this.#breed = breed;
}
speak() {
return `${super.speak()} - Woof!`;
}
}
const dog = new Dog('Rex', 'Labrador');
console.log(dog.speak()); // "Rex makes a sound - Woof!"
12. What is the difference between Object.freeze() and const?
const prevents reassignment of the variable binding. Object.freeze() prevents modification of object properties. Neither achieves deep immutability for nested objects.
const obj = { a: 1, nested: { b: 2 } };
// obj = {}; // Error - can't reassign const
obj.a = 2; // Allowed - const doesn't freeze the object
const frozen = Object.freeze({ a: 1, nested: { b: 2 } });
// frozen.a = 2; // Silently fails in non-strict mode, throws in strict mode
frozen.nested.b = 3; // Allowed - freeze is shallow!
// Deep freeze
function deepFreeze(obj) {
Object.getOwnPropertyNames(obj).forEach(name => {
const value = obj[name];
if (typeof value === 'object' && value !== null) {
deepFreeze(value);
}
});
return Object.freeze(obj);
}
Functional Programming
13. Explain map, filter, and reduce.
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// map: transforms each element
const doubled = numbers.map(n => n * 2);
// [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
// filter: selects elements matching a condition
const evens = numbers.filter(n => n % 2 === 0);
// [2, 4, 6, 8, 10]
// reduce: accumulates to a single value
const sum = numbers.reduce((acc, n) => acc + n, 0);
// 55
// Composing: sum of squares of even numbers
const result = numbers
.filter(n => n % 2 === 0)
.map(n => n ** 2)
.reduce((acc, n) => acc + n, 0);
// 220
14. What is function composition?
Function composition is combining multiple functions so the output of one is the input of the next.
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);
const double = x => x * 2;
const addOne = x => x + 1;
const square = x => x * x;
const transform = pipe(double, addOne, square);
console.log(transform(3)); // ((3 * 2) + 1)^2 = 49
15. What is currying?
Currying transforms a function with multiple arguments into a sequence of functions, each taking a single argument.
// Regular function
const add = (a, b, c) => a + b + c;
console.log(add(1, 2, 3)); // 6
// Curried version
const curriedAdd = a => b => c => a + b + c;
console.log(curriedAdd(1)(2)(3)); // 6
// Practical use: creating specialized functions
const multiply = a => b => a * b;
const double = multiply(2);
const triple = multiply(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
Modern JavaScript (ES2020β2026)
16. What is optional chaining (?.)?
Optional chaining allows accessing deeply nested properties without explicitly checking each level for null or undefined.
const user = {
profile: {
address: {
city: 'New York'
}
}
};
// Old way
const city = user && user.profile && user.profile.address && user.profile.address.city;
// Optional chaining
const city = user?.profile?.address?.city; // 'New York'
const zip = user?.profile?.address?.zip; // undefined (no error)
// With arrays and function calls
const firstName = user?.names?.[0];
const result = obj?.method?.();
17. What is nullish coalescing (??)?
?? returns the right-hand value only if the left-hand value is null or undefined (not other falsy values).
const config = {
timeout: 0, // Valid value, should not be defaulted
retries: null, // Explicitly null
name: '', // Empty string, valid
};
// || treats 0 and '' as falsy (wrong!)
console.log(config.timeout || 5000); // 5000 (wrong!)
console.log(config.name || 'default'); // 'default' (wrong!)
// ?? only defaults for null/undefined
console.log(config.timeout ?? 5000); // 0 (correct!)
console.log(config.name ?? 'default'); // '' (correct!)
console.log(config.retries ?? 3); // 3 (correct!)
18. Explain destructuring with default values and renaming.
const { name: firstName = 'Unknown', age = 0, role: userRole = 'user' } = {
name: 'Alice',
age: 30
};
console.log(firstName); // 'Alice'
console.log(age); // 30
console.log(userRole); // 'user' (default, and renamed from 'role')
// Array destructuring
const [first, second = 'default', ...rest] = [1, undefined, 3, 4];
console.log(first); // 1
console.log(second); // 'default' (undefined triggers default)
console.log(rest); // [3, 4]
// Nested destructuring
const { address: { city, country = 'US' } } = { address: { city: 'NYC' } };
19. What are generators?
Generators are functions that can pause execution and resume later, yielding values on demand.
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
// Practical: paginated data fetching
async function* fetchPages(url) {
let page = 1;
while (true) {
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
if (data.items.length === 0) break;
yield data.items;
page++;
}
}
for await (const items of fetchPages('/api/users')) {
processItems(items);
}
20. What is structuredClone?
structuredClone creates a deep clone of an object, handling circular references and complex types that JSON.parse(JSON.stringify()) cannot.
const original = {
date: new Date(),
map: new Map([['key', 'value']]),
set: new Set([1, 2, 3]),
array: [1, [2, 3]],
regex: /test/gi
};
// JSON approach fails for these types
const badClone = JSON.parse(JSON.stringify(original));
// date becomes string, Map/Set become {}, RegExp becomes {}
// structuredClone handles them correctly
const goodClone = structuredClone(original);
goodClone.array[1].push(4);
console.log(original.array[1]); // [2, 3] β not affected
React-Specific Questions
21. Explain the difference between useEffect with no deps, empty array, and with deps.
// Runs after EVERY render
useEffect(() => {
document.title = `${count} clicks`;
});
// Runs only once after initial render (component mount)
useEffect(() => {
fetchInitialData();
return () => cleanup(); // Cleanup on unmount
}, []);
// Runs when count or userId changes
useEffect(() => {
fetchUserData(userId);
}, [userId, count]);
22. What is the difference between useMemo and useCallback?
// useMemo: memoizes a computed VALUE
const sortedList = useMemo(() => {
return [...items].sort((a, b) => a.name.localeCompare(b.name));
}, [items]); // Only re-sorts when items changes
// useCallback: memoizes a FUNCTION reference
const handleClick = useCallback((id) => {
setSelected(id);
onSelect(id); // Close over stable reference
}, [onSelect]); // Stable reference unless onSelect changes
Use useMemo for expensive calculations. Use useCallback when passing callbacks to child components optimized with React.memo.
23. What are React Server Components?
React Server Components (RSC) run on the server during rendering and are never sent to the client as JavaScript. They can directly access databases, file systems, and private APIs.
// server-component.tsx (Server Component)
async function UserProfile({ userId }) {
// Direct database access β no API needed
const user = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
const posts = await db.query('SELECT * FROM posts WHERE user_id = ?', [userId]);
return (
<div>
<h1>{user.name}</h1>
<PostList posts={posts} />
<InteractiveWidget userId={userId} /> {/* Client Component */}
</div>
);
}
Server Components reduce client bundle size and improve performance for content-heavy pages.
TypeScript Questions
24. What is the difference between interface and type?
Both define shapes, but with differences:
// interface: better for object shapes, can be extended, can be merged
interface User {
name: string;
age: number;
}
interface User { // Declaration merging
email: string;
}
interface Admin extends User {
role: string;
}
// type: more flexible, can represent any type, cannot be merged
type StringOrNumber = string | number;
type Point = [number, number];
type UserTuple = [string, number, boolean];
// For most object shapes, prefer interface
// For unions, intersections, or non-object types, use type
25. Explain generics with a practical example.
// Without generics - not type safe
function getFirst(arr: any[]): any {
return arr[0];
}
// With generics - maintains type information
function getFirst<T>(arr: T[]): T | undefined {
return arr[0];
}
const firstNum = getFirst([1, 2, 3]); // type: number
const firstStr = getFirst(['a', 'b']); // type: string
// Generic with constraints
function sortBy<T, K extends keyof T>(arr: T[], key: K): T[] {
return [...arr].sort((a, b) => {
if (a[key] < b[key]) return -1;
if (a[key] > b[key]) return 1;
return 0;
});
}
const sorted = sortBy(users, 'name'); // Enforces 'name' is a key of user
Performance and Optimization
26. What techniques improve JavaScript performance?
Memory management:
- Avoid memory leaks in closures and event listeners
- Use
WeakMapandWeakSetfor object-keyed caches - Clear intervals and timeouts when components unmount
Execution efficiency:
- Debounce and throttle expensive event handlers
- Use Web Workers for CPU-intensive tasks
- Prefer
requestAnimationFramefor animations - Batch DOM operations
Bundle size:
- Tree shaking to remove unused code
- Code splitting and lazy loading
- Use modern build tools (Vite, esbuild)
// Debounce: wait until user stops typing
const handleSearch = debounce((query) => {
fetchResults(query);
}, 300);
// Throttle: limit to once per 100ms
const handleScroll = throttle((event) => {
updateScrollPosition(event);
}, 100);
// Web Worker for heavy computation
const worker = new Worker('heavy-computation.js');
worker.postMessage({ data: largeDataset });
worker.onmessage = (e) => updateUI(e.data);
27. Explain how to avoid common memory leaks.
// Memory leak: event listener not removed
class Component {
constructor() {
this.handleResize = this.handleResize.bind(this);
window.addEventListener('resize', this.handleResize);
}
// WRONG: no cleanup
destroy() {}
// RIGHT: remove listener
destroy() {
window.removeEventListener('resize', this.handleResize);
}
}
// Memory leak: setInterval not cleared
const intervalId = setInterval(() => updateData(), 1000);
// Must call clearInterval(intervalId) when done
// Memory leak: closure holding large reference
function createLeak() {
const hugeData = new Array(1000000).fill('data');
return function() {
// hugeData stays in memory even if only using a small piece
return hugeData[0];
};
}
Testing
28. What is the difference between unit tests, integration tests, and end-to-end tests?
- Unit tests: Test a single function or component in isolation. Fast, many of them.
- Integration tests: Test how multiple units work together. Medium speed.
- End-to-end tests: Test entire user flows through the real application. Slow, fewer needed.
Follow the testing pyramid: many unit tests, fewer integration tests, minimal end-to-end tests.
// Unit test (Vitest/Jest)
describe('calculateTax', () => {
it('applies 20% VAT to positive amounts', () => {
expect(calculateTax(100, 0.2)).toBe(20);
});
it('returns 0 for zero amount', () => {
expect(calculateTax(0, 0.2)).toBe(0);
});
});
// Integration test
describe('UserRepository', () => {
it('creates and retrieves a user', async () => {
const user = await repo.create({ name: 'Alice', email: 'alice@example.com' });
const found = await repo.findById(user.id);
expect(found.name).toBe('Alice');
});
});
System Design for Interviews
29. How would you design a rate limiter in JavaScript?
class RateLimiter {
constructor(maxRequests, windowMs) {
this.maxRequests = maxRequests;
this.windowMs = windowMs;
this.requests = new Map(); // userId -> [timestamps]
}
isAllowed(userId) {
const now = Date.now();
const windowStart = now - this.windowMs;
const userRequests = this.requests.get(userId) || [];
const validRequests = userRequests.filter(time => time > windowStart);
if (validRequests.length >= this.maxRequests) {
this.requests.set(userId, validRequests);
return false;
}
validRequests.push(now);
this.requests.set(userId, validRequests);
return true;
}
}
const limiter = new RateLimiter(10, 60000); // 10 requests per minute
30. How would you implement a LRU cache?
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.cache = new Map(); // Map preserves insertion order
}
get(key) {
if (!this.cache.has(key)) return -1;
// Move to end (most recently used)
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
put(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size >= this.capacity) {
// Delete least recently used (first item in Map)
this.cache.delete(this.cache.keys().next().value);
}
this.cache.set(key, value);
}
}
const cache = new LRUCache(3);
cache.put('a', 1);
cache.put('b', 2);
cache.put('c', 3);
cache.get('a'); // Moves 'a' to end
cache.put('d', 4); // Evicts 'b' (least recently used)
Interview Tips
Thinking Through Problems
- Clarify requirements before writing code β ask about edge cases and constraints
- Explain your approach before implementing
- Start simple, then optimize
- Test with examples β walk through your code manually
- Discuss trade-offs β there are rarely perfect solutions
Common Mistakes to Avoid
- Forgetting edge cases (empty arrays, null inputs, integer overflow)
- Choosing wrong data structures (array when Map would be O(1) vs O(n))
- Not handling async errors
- Mutating inputs (always clarify if mutation is acceptable)
Code Quality Red Flags
- Deeply nested callbacks (use async/await)
- Magic numbers and strings (use named constants)
- Functions longer than 30 lines (extract sub-functions)
- Not handling errors
Practice These Concepts
Use our free tools to test and debug code concepts:
- Regex Tester β Practice regular expressions used in interviews
- JSON Formatter β Format and understand data structures
- Base64 Tool β Understand encoding used in authentication
- Hash Generator β Explore cryptographic concepts