JavaScript interviews have shifted. Five years ago, you could get by explaining hoisting and the event loop. Today, interviewers expect you to understand modern patterns, performance tradeoffs, and real-world application architecture.
Here are the 50 questions that actually come up - organized from fundamentals to advanced. Each answer focuses on what interviewers are really evaluating. Working in another language? See our top 50 Java interview questions and top 50 Python interview questions too.
Core Language (1-10)
1. What's the difference between let, const, and var?
var is function-scoped and hoisted. let and const are block-scoped and sit in the temporal dead zone until declared. const prevents reassignment but doesn't make objects immutable.
The real interview answer: "I use const by default, let when I need reassignment, and var never. Block scoping prevents an entire category of bugs."
2. Explain closures with a practical example.
A closure is a function that remembers variables from its outer scope even after that scope has finished executing.
function createCounter(initial = 0) {
let count = initial;
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count
};
}
const counter = createCounter(10);
counter.increment();
counter.increment();
counter.getCount();
Closures power module patterns, data privacy, partial application, and most callback patterns. If you understand closures, you understand half of JavaScript's design.
3. What is the event loop and how does it work?
JavaScript is single-threaded. The event loop manages concurrency by processing a call stack, a microtask queue (Promises, queueMicrotask), and a macrotask queue (setTimeout, setInterval, I/O).
Order of execution:
- Synchronous code on the call stack
- All microtasks (Promise callbacks,
queueMicrotask)
- One macrotask
- All microtasks again
- Repeat
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
4. What's the difference between == and ===?
== performs type coercion before comparison. === checks value and type without coercion.
0 == ''
0 === ''
null == undefined
null === undefined
Always use ===. The coercion rules for == are a source of bugs that no one can memorize reliably.
5. How does this work in JavaScript?
this depends on how a function is called, not where it's defined:
- Regular function call:
this is undefined (strict mode) or globalThis
- Method call:
this is the object before the dot
new keyword: this is the newly created object
call/apply/bind: this is explicitly set
- Arrow function:
this is inherited from the enclosing scope (lexical this)
const obj = {
name: 'Alice',
greet() { return this.name; },
greetArrow: () => this.name,
delayedGreet() {
setTimeout(() => console.log(this.name), 100);
}
};
Arrow functions solving the this problem is one of the most important changes in modern JavaScript.
6. Explain prototypal inheritance.
Every JavaScript object has a prototype chain. When you access a property, the engine walks up the chain until it finds it or hits null.
const animal = { speak() { return `${this.name} makes a sound`; } };
const dog = Object.create(animal);
dog.name = 'Rex';
dog.speak();
Modern JavaScript uses class syntax, but it's syntactic sugar over prototypes. Understanding the chain helps you debug unexpected behavior and optimize performance.
7. What are the different ways to create objects?
const obj1 = { key: 'value' };
const obj2 = new Object();
const obj3 = Object.create(proto);
class MyClass { constructor() { this.key = 'value'; } }
const obj4 = new MyClass();
const createObj = (key) => ({ key });
const obj5 = createObj('value');
In practice, object literals and classes cover 99% of use cases.
8. What is hoisting?
Hoisting moves declarations (not initializations) to the top of their scope during compilation.
console.log(x);
var x = 5;
console.log(y);
let y = 5;
hello();
function hello() { return 'hi'; }
goodbye();
var goodbye = function() { return 'bye'; };
9. What are generators and when would you use them?
Generators are functions that can pause and resume, yielding values on demand.
function* paginate(items, pageSize) {
for (let i = 0; i < items.length; i += pageSize) {
yield items.slice(i, i + pageSize);
}
}
const pager = paginate([1,2,3,4,5,6,7,8,9,10], 3);
pager.next();
pager.next();
Real uses: lazy evaluation, infinite sequences, custom iterables, and managing complex async flows with libraries like Redux-Saga.
10. Explain the difference between shallow copy and deep copy.
const original = { a: 1, nested: { b: 2 } };
const shallow = { ...original };
shallow.nested.b = 99;
console.log(original.nested.b);
const deep = structuredClone(original);
deep.nested.b = 99;
console.log(original.nested.b);
Use structuredClone() for deep copies. It handles circular references, Maps, Sets, and most built-in types. It's been available in all browsers and Node.js since 2022.
Functions & Scope (11-18)
11. What are arrow functions and how do they differ from regular functions?
Arrow functions are concise, but the differences go beyond syntax:
- No own
this binding (lexical this)
- No
arguments object (use rest parameters)
- Cannot be used as constructors (
new throws)
- No
prototype property
- Cannot be used as generator functions
Use arrow functions for callbacks and short expressions. Use regular functions for methods and constructors.
12. Explain call, apply, and bind.
All three set this explicitly:
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const user = { name: 'Alice' };
greet.call(user, 'Hello', '!');
greet.apply(user, ['Hello', '!']);
const bound = greet.bind(user, 'Hello');
bound('!');
call invokes immediately with individual args. apply invokes immediately with an array. bind returns a new function with this permanently set.
13. What is currying?
Currying transforms a function with multiple arguments into a sequence of functions each taking one argument.
const multiply = (a) => (b) => a * b;
const double = multiply(2);
const triple = multiply(3);
double(5);
triple(5);
Useful for creating specialized functions from general ones, middleware composition, and functional programming patterns.
14. What are pure functions and why do they matter?
A pure function always returns the same output for the same input and has no side effects.
const add = (a, b) => a + b;
let total = 0;
const addToTotal = (n) => total += n;
Pure functions are easier to test, debug, cache, and parallelize. React's rendering model is built on this principle.
15. Explain the module system (import/export).
export const API_URL = '/api';
export function fetchUser(id) { }
export default class UserService { }
import UserService, { API_URL, fetchUser } from './user-service.js';
const module = await import('./heavy-module.js');
ES modules are statically analyzed (enabling tree-shaking), loaded asynchronously, and have strict mode by default. CommonJS (require) is still used in Node.js but ESM is the standard going forward.
16. What is the temporal dead zone?
The TDZ is the period between entering a scope and the variable's declaration being processed. Accessing a let or const variable in its TDZ throws a ReferenceError.
{
console.log(x);
let x = 5;
}
This catches bugs that var would silently allow by returning undefined.
17. What are rest parameters and the spread operator?
Same syntax (...), different contexts:
function sum(...numbers) {
return numbers.reduce((a, b) => a + b, 0);
}
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];
const defaults = { theme: 'dark', lang: 'en' };
const settings = { ...defaults, lang: 'fr' };
18. What is memoization?
Memoization caches function results based on arguments to avoid redundant computation.
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 expensiveFib = memoize(function fib(n) {
if (n <= 1) return n;
return expensiveFib(n - 1) + expensiveFib(n - 2);
});
React's useMemo and React.memo are built on this concept.
Async JavaScript (19-26)
19. What are Promises?
A Promise represents a value that may not exist yet. It's in one of three states: pending, fulfilled, or rejected.
function fetchData(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
})
.then(resolve)
.catch(reject);
});
}
Key methods: .then(), .catch(), .finally(), and the static methods Promise.all(), Promise.allSettled(), Promise.race(), Promise.any().
20. Explain async/await.
async/await is syntactic sugar over Promises that makes asynchronous code read like synchronous code.
async function getUserPosts(userId) {
try {
const user = await fetchUser(userId);
const posts = await fetchPosts(user.id);
return posts;
} catch (error) {
console.error('Failed to load posts:', error);
return [];
}
}
Common mistake: using await sequentially when operations are independent. Use Promise.all for parallel execution:
const user = await fetchUser(id);
const posts = await fetchPosts(id);
const [user, posts] = await Promise.all([
fetchUser(id),
fetchPosts(id)
]);
21. What's the difference between Promise.all, Promise.allSettled, Promise.race, and Promise.any?
const p1 = Promise.resolve(1);
const p2 = Promise.reject('error');
const p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]);
Promise.allSettled([p1, p2, p3]);
Promise.race([p1, p2, p3]);
Promise.any([p1, p2, p3]);
22. How do you handle errors in async code?
async function loadData() {
try {
const data = await fetchData();
return process(data);
} catch (error) {
if (error instanceof NetworkError) {
return getCachedData();
}
throw error;
}
}
fetchData()
.then(process)
.catch(handleError);
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason);
});
23. What is the fetch API and how has it evolved?
const response = await fetch('/api/users');
const data = await response.json();
await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Alice' }),
signal: AbortSignal.timeout(5000)
});
Modern additions: AbortSignal.timeout(), request streaming, and the Response.json() static method.
24. What are Web Workers?
Web Workers run JavaScript in a background thread, keeping the main thread responsive.
const worker = new Worker('worker.js');
worker.postMessage({ data: largeArray });
worker.onmessage = (event) => {
console.log('Result:', event.data);
};
self.onmessage = (event) => {
const result = heavyComputation(event.data);
self.postMessage(result);
};
Use cases: data processing, image manipulation, encryption, parsing large files. Workers can't access the DOM.
25. Explain the AbortController pattern.
AbortController lets you cancel async operations - most commonly fetch requests.
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch('/api/data', {
signal: controller.signal
});
const data = await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request was cancelled');
}
}
Essential for React cleanup in useEffect to prevent state updates on unmounted components.
26. What are async iterators?
Async iterators let you consume asynchronous data sources with for await...of.
async function* streamLines(url) {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop();
for (const line of lines) yield line;
}
if (buffer) yield buffer;
}
for await (const line of streamLines('/api/logs')) {
console.log(line);
}
Objects & Data Structures (27-33)
27. What are Map and Set?
const map = new Map();
map.set({ id: 1 }, 'user object as key');
map.set(42, 'number as key');
map.size;
const set = new Set([1, 2, 2, 3, 3]);
[...set];
const unique = [...new Set(arrayWithDuplicates)];
Use Map over plain objects when keys aren't strings or you need to track size. Use Set when you need uniqueness guarantees.
28. What are WeakMap and WeakSet?
Weak collections hold "weak" references that don't prevent garbage collection.
const cache = new WeakMap();
function getMetadata(element) {
if (cache.has(element)) return cache.get(element);
const metadata = computeMetadata(element);
cache.set(element, metadata);
return metadata;
}
Key limitation: not iterable, no .size. Use them for caching and private data attached to objects.
29. Explain destructuring.
const [first, second, ...rest] = [1, 2, 3, 4, 5];
const { name: userName, age = 25, role = 'user' } = user;
const { address: { city, zip } } = user;
function createUser({ name, email, role = 'user' }) {
return { name, email, role };
}
30. What is optional chaining and nullish coalescing?
const city = user?.address?.city;
const first = arr?.[0];
const result = obj?.method?.();
const port = config.port ?? 3000;
const count = data.count || 10;
const count = data.count ?? 10;
31. How does JSON.parse/JSON.stringify work and what are the gotchas?
const json = JSON.stringify({ name: 'Alice', age: 30 });
const obj = JSON.parse(json);
JSON.stringify(undefined);
JSON.stringify({ fn: () => {} });
JSON.stringify({ date: new Date() });
JSON.parse(json, (key, value) => {
if (key === 'date') return new Date(value);
return value;
});
JSON.stringify(obj, (key, value) => {
if (key === 'password') return undefined;
return value;
}, 2);
32. What are Symbols and when do you use them?
Symbols create unique identifiers that can't collide with other property keys.
const id = Symbol('id');
const user = { [id]: 123, name: 'Alice' };
user[id];
Object.keys(user);
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
return {
next() {
return current <= end
? { value: current++, done: false }
: { done: true };
}
};
}
}
for (const n of new Range(1, 5)) console.log(n);
33. What is the Proxy object?
Proxies intercept and customize fundamental operations on objects.
const validator = new Proxy({}, {
set(target, property, value) {
if (property === 'age' && typeof value !== 'number') {
throw new TypeError('Age must be a number');
}
if (property === 'age' && (value < 0 || value > 150)) {
throw new RangeError('Age must be between 0 and 150');
}
target[property] = value;
return true;
}
});
validator.age = 25;
validator.age = 'old';
validator.age = -1;
Used in Vue 3's reactivity system, validation libraries, and API clients.
DOM & Browser APIs (34-38)
34. What is event delegation?
Instead of attaching listeners to each child element, attach one to the parent and use the event's target to determine which child was clicked.
document.querySelector('.todo-list').addEventListener('click', (e) => {
const item = e.target.closest('.todo-item');
if (!item) return;
if (e.target.matches('.delete-btn')) {
item.remove();
} else if (e.target.matches('.toggle-btn')) {
item.classList.toggle('completed');
}
});
Benefits: fewer event listeners, automatically works for dynamically added elements, better memory usage.
35. What is the difference between localStorage, sessionStorage, and cookies?
| Feature | localStorage | sessionStorage | Cookies |
|---|
| Capacity | ~5-10MB | ~5-10MB | ~4KB |
| Expiry | Never | Tab close | Configurable |
| Sent to server | No | No | Every request |
| Scope | Origin | Origin + tab | Origin + path |
localStorage.setItem('theme', 'dark');
const theme = localStorage.getItem('theme');
sessionStorage.setItem('formDraft', JSON.stringify(formData));
Use localStorage for user preferences, sessionStorage for temporary form data, and cookies for server-side auth tokens.
36. What is the Intersection Observer API?
Efficiently detects when elements enter or leave the viewport.
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.src = entry.target.dataset.src;
observer.unobserve(entry.target);
}
});
}, { threshold: 0.1 });
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});
Use cases: lazy loading images, infinite scroll, analytics (tracking what users actually see), and triggering animations.
37. Explain event bubbling and capturing.
Events propagate in three phases: capture (top-down), target, and bubble (bottom-up).
parent.addEventListener('click', handler);
child.addEventListener('click', handler);
parent.addEventListener('click', handler, true);
child.addEventListener('click', handler, true);
child.addEventListener('click', (e) => {
e.stopPropagation();
});
38. What are service workers?
Service workers are proxy scripts between the browser and network, enabling offline functionality and push notifications.
navigator.serviceWorker.register('/sw.js');
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then(cache =>
cache.addAll(['/index.html', '/styles.css', '/app.js'])
)
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
});
Modern JavaScript & Patterns (39-50)
39. What are template literals and tagged templates?
const greeting = `Hello, ${name}! You have ${count} messages.`;
const html = `
<div class="card">
<h2>${title}</h2>
<p>${description}</p>
</div>
`;
function sql(strings, ...values) {
return {
text: strings.join('$'),
values: values
};
}
const query = sql`SELECT * FROM users WHERE id = ${userId} AND active = ${true}`;
Tagged templates power libraries like styled-components and SQL query builders.
40. What are decorators?
Decorators (Stage 3, widely supported via TypeScript and Babel) add metadata or modify classes and methods.
function logged(originalMethod, context) {
return function(...args) {
console.log(`Calling ${context.name} with`, args);
const result = originalMethod.apply(this, args);
console.log(`${context.name} returned`, result);
return result;
};
}
class Calculator {
@logged
add(a, b) { return a + b; }
}
41. What is tree-shaking?
Tree-shaking removes unused code during bundling. It works because ES modules have static import/export declarations that can be analyzed at build time.
export function add(a, b) { return a + b; }
export function multiply(a, b) { return a * b; }
import { add } from './math.js';
For tree-shaking to work: use ES modules (not CommonJS), avoid side effects in modules, and mark packages as side-effect-free in package.json.
42. What design patterns do you use in JavaScript?
Observer pattern:
class EventEmitter {
#listeners = new Map();
on(event, callback) {
if (!this.#listeners.has(event)) this.#listeners.set(event, []);
this.#listeners.get(event).push(callback);
}
emit(event, ...args) {
this.#listeners.get(event)?.forEach(cb => cb(...args));
}
}
Module pattern (encapsulation via closures or ES modules), Factory pattern (creating objects without new), and Strategy pattern (swappable algorithms) are the most common in modern JS.
43. What are private class fields?
class BankAccount {
#balance = 0;
deposit(amount) {
if (amount <= 0) throw new Error('Invalid amount');
this.#balance += amount;
}
get balance() { return this.#balance; }
}
const account = new BankAccount();
account.deposit(100);
account.balance;
account.#balance;
Unlike the _underscore convention, # fields are enforced by the engine. They can't be accessed outside the class.
44. What is the structuredClone API?
structuredClone() creates deep copies of values, handling cases that JSON.parse(JSON.stringify()) can't:
const original = {
date: new Date(),
regex: /test/gi,
set: new Set([1, 2, 3]),
nested: { deep: { value: 42 } }
};
const clone = structuredClone(original);
clone.date instanceof Date;
clone.set instanceof Set;
Limitations: can't clone functions, DOM nodes, or objects with prototype chains.
45. How does error handling work with Error types?
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
try {
await fetchUserData();
} catch (error) {
throw new Error('Failed to load user profile', { cause: error });
}
try {
validateInput(data);
} catch (error) {
if (error instanceof ValidationError) {
showFieldError(error.field, error.message);
} else {
throw error;
}
}
46. What are Records and Tuples?
Records and Tuples (Stage 2 proposal) are deeply immutable data structures that compare by value:
const record = #{ name: 'Alice', age: 30 };
const tuple = #[1, 2, 3];
#{ a: 1 } === #{ a: 1 }
#[1, 2] === #[1, 2]
Until they ship, use Object.freeze() for shallow immutability or libraries like Immer for immutable update patterns.
47. What is the Iterator protocol?
Any object with a [Symbol.iterator]() method that returns { next() } is iterable:
class Fibonacci {
[Symbol.iterator]() {
let [a, b] = [0, 1];
return {
next() {
[a, b] = [b, a + b];
return { value: a, done: false };
}
};
}
}
const fibs = [];
for (const n of new Fibonacci()) {
if (fibs.length >= 10) break;
fibs.push(n);
}
48. How do you handle memory leaks in JavaScript?
Common sources and fixes:
const handler = () => { };
element.addEventListener('click', handler);
function createHandler(heavyData) {
return () => console.log(heavyData);
}
const id = setInterval(() => updateDashboard(), 1000);
const nodes = [];
nodes.push(document.createElement('div'));
Use Chrome DevTools Memory tab to identify leaks: take heap snapshots, compare, and look for growing retained sizes.
49. What's new in recent ECMAScript versions?
ES2024:
Object.groupBy() and Map.groupBy()
Promise.withResolvers()
- Well-formed Unicode strings
ES2025:
Set methods: union(), intersection(), difference(), symmetricDifference()
Iterator.prototype methods: .map(), .filter(), .take(), .drop()
RegExp escaping
const people = [{ name: 'Alice', dept: 'eng' }, { name: 'Bob', dept: 'eng' }];
Object.groupBy(people, p => p.dept);
new Set([1,2,3]).union(new Set([3,4,5]));
Rendering:
- Use
requestAnimationFrame for visual updates
- Batch DOM reads/writes to avoid layout thrashing
- Use CSS
contain and content-visibility for rendering optimization
JavaScript:
- Debounce/throttle expensive event handlers
- Use Web Workers for CPU-intensive tasks
- Lazy load modules with dynamic
import()
- Use
Map/Set for frequent lookups instead of arrays
Network:
- Preload critical resources with
<link rel="preload">
- Use
loading="lazy" on images
- Implement request deduplication and caching
Measurement:
performance.mark('start');
doExpensiveWork();
performance.mark('end');
performance.measure('work', 'start', 'end');
console.log(performance.getEntriesByName('work')[0].duration);
How to Use This List
Don't memorize answers. Understand the concepts well enough to explain them in your own words and apply them to real problems.
For each topic, ask yourself:
- Can I explain this to someone who hasn't seen it before?
- Can I write a working example without looking anything up?
- Do I know when I'd use this in a real project?
If you can do all three, you're ready.
Practice JavaScript interview questions with real-time feedback at gitGood.dev - work through practice questions and coding challenges, then test yourself in an AI mock interview. Build the skills that actually get you hired.