Interview Prep18 min read · Updated 2026-03-09

Top 50 JavaScript Interview Questions (With Deep Answers)

Not a list of definitions — these are the 50 questions that actually separate candidates in frontend interviews, with the kind of answer that makes interviewers write "strong hire."

💡 Practice these concepts interactively with AI feedback

Start Practicing →

Most "top 50" lists give you dictionary definitions. An interviewer asks you about closures and you recite "a function that remembers its outer scope" — and they move on, unimpressed. What actually earns the offer is understanding why something works, demonstrating it with code, and knowing the edge cases.

These 50 questions are organized by concept. Every answer goes beyond the surface.

Core Language & Types

1. What are JavaScript's primitive types?

There are 7: string, number, bigint, boolean, undefined, null, and symbol. Everything else — arrays, functions, objects — is an object. The critical distinction: primitives are compared by value, objects by reference. 'hello' === 'hello' is true. {} === {} is false — two different objects in memory.

2. What is typeof null and why?

typeof null returns 'object' — widely acknowledged as a bug in JavaScript that can never be fixed for backwards compatibility. The original V8 implementation used a type tag in the low bits of a value's memory representation, and null's all-zero bit pattern matched the object tag. Test for null explicitly: value === null.

3. What is the difference between == and ===?

=== (strict equality) compares type AND value — no conversion. == (loose equality) performs type coercion before comparing, following a complex set of rules. 0 == false is true. null == undefined is true. [] == false is true. In production code, use === always. Know == for interview output questions.

4. What does NaN === NaN return, and how do you check for NaN?

false — NaN is the only JavaScript value that is not equal to itself. Use Number.isNaN(value) for a reliable check. Avoid the global isNaN() which coerces its argument first (isNaN('hello') returns true, Number.isNaN('hello') returns false).

5. What is the difference between undefined and null?

undefined means a variable was declared but never assigned — the JavaScript engine set it. null means intentionally empty — a developer set it. typeof undefined is 'undefined'. typeof null is 'object'. undefined == null is true, undefined === null is false.

Scope & Closures

6. What is a closure?

A closure is a function that retains access to its lexical scope — the variables of the outer function where it was defined — even after that outer function has returned. The classic example: a counter factory. The returned increment function closes over the count variable. Each factory call creates an independent count. Closures are how JavaScript achieves data privacy without classes.

function makeCounter() {
  let count = 0
  return {
    increment: () => ++count,
    decrement: () => --count,
    value:     () => count,
  }
}
const a = makeCounter()
const b = makeCounter()
a.increment(); a.increment()
b.increment()
a.value() // 2 — independent from b
b.value() // 1

7. What is the scope chain?

When JavaScript resolves a variable, it first looks in the current function's scope. If not found, it moves to the enclosing function's scope, then that function's enclosing scope, continuing up until it reaches the global scope. If still not found: ReferenceError. This chain of scopes is fixed at the time the function is written (lexical scope) — not at the time it is called.

8. What is the classic loop closure bug?

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100)
}
// Logs: 3, 3, 3 — NOT 0, 1, 2

All three callbacks share the same var i — there's only one variable, and by the time the callbacks fire, the loop has finished and i is 3. Fix: use let (block-scoped, new binding per iteration) or an IIFE to capture the current value.

9. What is lexical scope?

Scope determined at write time by where functions are defined in the source code — not where they're called from. A function's scope chain is fixed when it's created. This is why a function defined inside another function can access the outer function's variables, regardless of where the inner function is later called.

10. What is the Temporal Dead Zone?

let and const declarations are hoisted but not initialized. Between the start of the enclosing block and the declaration line, the variable exists but cannot be read or written — accessing it throws a ReferenceError. This zone is the TDZ. It exists to prevent the confusing undefined behavior that var produces when accessed before its declaration.

Hoisting

11. What gets hoisted in JavaScript?

var declarations are hoisted and initialized to undefined. Function declarations are hoisted completely — name and body. let and const are hoisted but remain in the TDZ until their declaration. Class declarations are hoisted but also TDZ-restricted. Function expressions (const fn = function() {}) are not hoisted — only the variable declaration is.

12. What does this output?

console.log(foo())
console.log(bar)

function foo() { return 42 } var bar = 'hello'

foo() logs 42 — function declaration is fully hoisted. bar logs undefined — the var declaration is hoisted but not the assignment. The assignment bar = 'hello' hasn't run yet.

this Keyword

13. What does this refer to?

this is determined by how a function is called, not where it's defined. Five rules in priority order: new binding → explicit binding (call/apply/bind) → implicit binding (method call: obj.method()) → default binding (standalone function call → global or undefined in strict mode) → arrow functions (no own this — inherit from lexical scope).

14. What is the difference between call, apply, and bind?

All three let you set this explicitly. call(ctx, arg1, arg2) invokes immediately with individual arguments. apply(ctx, [arg1, arg2]) invokes immediately with an array of arguments. bind(ctx, arg1) returns a new function with this permanently bound — useful for event handlers and callbacks where you need to preserve context.

15. Why do arrow functions not have their own this?

Arrow functions don't create their own this binding at all. They capture this from the lexical scope where they're defined. This makes them perfect for callbacks inside methods, where a regular function would lose the object's this. But it makes them wrong for object methods, prototype methods, or constructors — because they can't be this-bound.

Prototypes & Inheritance

16. How does prototypal inheritance work?

Every object has a hidden [[Prototype]] link to another object. When you access a property that doesn't exist on the object, JavaScript follows the chain to the prototype, then to its prototype, continuing until null is reached. Methods on Array.prototype, String.prototype, and Object.prototype are available on every array, string, and object through this chain.

17. What does new do?

Four things: creates a new empty object, sets its [[Prototype]] to Constructor.prototype, calls the constructor with this set to the new object, and returns the new object (unless the constructor explicitly returns a different object). Understanding this is how you implement new from scratch in an interview.

18. What is the difference between Object.create() and new?

Object.create(proto) creates a new object with proto as its [[Prototype]] directly — no constructor function involved. new Constructor() creates an object prototypically linked to Constructor.prototype AND runs the constructor. Object.create(null) creates an object with no prototype at all — useful as a safe dictionary.

19. How do ES6 classes relate to prototypes?

Classes are syntactic sugar. class Foo {} creates a function Foo. Methods defined in the class body go on Foo.prototype. extends sets up the prototype chain. super() calls the parent constructor. Everything is the same prototype mechanism underneath — typeof MyClass === 'function' proves it.

Async JavaScript

20. What is the event loop?

The mechanism that coordinates JavaScript's single thread with asynchronous operations. It continuously checks: if the call stack is empty, drain the entire microtask queue (Promise callbacks), then pick one macrotask (setTimeout callback, I/O event), execute it, and repeat. This cycle is why all Promise callbacks run before any setTimeout callback, even with a 0ms delay.

21. What is the output of this code?

console.log('1')
setTimeout(() => console.log('2'), 0)
Promise.resolve().then(() => console.log('3'))
console.log('4')

1, 4, 3, 2. Sync first (1, 4), then microtask (3), then macrotask (2). The setTimeout fires last even with 0ms delay because it's a macrotask and waits for all microtasks to drain first.

22. What is a Promise and what are its three states?

A Promise is an object representing an eventually available value. States: pending (initial), fulfilled (resolved successfully), rejected (failed). State transitions are permanent — a settled Promise never changes. Every .then() returns a new Promise, enabling chaining. .catch() catches any rejection from anywhere in the chain above it.

23. What is the difference between Promise.all, Promise.allSettled, Promise.race, and Promise.any?

Promise.all: resolves when all resolve (in input order), rejects immediately if any reject. Promise.allSettled: always resolves with an array of outcome objects — never rejects. Use when you need all results regardless of failures. Promise.race: settles with the first to settle (fulfilled or rejected). Promise.any: resolves with the first fulfillment; only rejects if all reject.

24. What does async/await compile down to?

Syntactic sugar over Promises. An async function always returns a Promise. await pauses the async function and registers the rest as a .then() callback — the thread is never actually blocked. try/catch in async functions catches Promise rejections the same way .catch() does.

25. What is the sequential vs parallel await mistake?

// Sequential — slow: total = sum of all times
const a = await fetchA()  // waits
const b = await fetchB()  // waits AFTER a

// Parallel — fast: total = slowest one const [a, b] = await Promise.all([fetchA(), fetchB()])

This is the most common and expensive async mistake in production code.

Functions

26. What is the difference between function declarations and expressions?

Function declarations (function foo() {}) are fully hoisted — callable before their definition in the source. Function expressions (const foo = function() {} or const foo = () => {}) are not hoisted — the variable is hoisted (to undefined), but the function assignment is not. Declarations can't be conditionally defined in sloppy mode; expressions can.

27. What is a higher-order function?

A function that takes a function as an argument, returns a function, or both. Array.map, filter, reduce are higher-order functions. They're the core primitive of functional programming in JavaScript. Understanding them means understanding that functions are first-class values — they can be passed, returned, and stored just like numbers or strings.

28. What is currying?

Transforming a function that takes multiple arguments into a series of functions that each take one argument. add(1, 2) becomes add(1)(2). Currying enables partial application — creating specialized functions by pre-supplying arguments. It's the foundation of function composition in functional programming libraries like Ramda and fp-ts.

29. What is the difference between arguments and rest parameters?

arguments is an array-like object (not a real array) available in regular functions — it contains all arguments. Rest parameters (...args) create a real Array from remaining arguments and work in arrow functions. arguments doesn't exist in arrow functions at all. Rest parameters are specific (only capture "the rest"), composable, and have all Array methods.

30. What are pure functions?

Functions that: (1) given the same inputs, always return the same output, and (2) produce no side effects (no mutation of external state, no I/O). Pure functions are predictable, testable in isolation, and safe to memoize. React components and Redux reducers are expected to be pure — breaking this expectation causes subtle, hard-to-reproduce bugs.

Arrays

31. What is the difference between map, filter, and reduce?

map transforms every element, returning a new array of the same length. filter returns a new array containing only elements for which the callback returned truthy. reduce accumulates elements into a single value — it's the most general of the three; map and filter can both be implemented with reduce.

32. What does Array.sort() do by default and why is it dangerous?

Converts elements to strings and sorts lexicographically. [10, 9, 2, 1, 100].sort() returns [1, 10, 100, 2, 9] — "10" comes before "2" alphabetically. Always pass a comparator for numbers: .sort((a, b) => a - b). Also: sort is in-place and mutates the original array. ES2023's toSorted() returns a new array without mutation.

33. What is the difference between splice and slice?

splice mutates the original array — it removes elements, inserts elements, or both, and returns the removed elements. slice never mutates — it returns a shallow copy of a portion of the array. Memory aid: splice = edit, slice = copy.

34. How do you flatten a nested array?

arr.flat(depth) — default depth is 1. arr.flat(Infinity) flattens completely. arr.flatMap(fn) maps then flattens one level in a single pass — more efficient than .map().flat().

35. What is the difference between find and filter?

find returns the first matching element (or undefined). filter returns an array of all matching elements (possibly empty). For single-item lookups, find is both semantically clearer and slightly more efficient since it stops at the first match.

Objects & Classes

36. What is the difference between shallow and deep copying an object?

Shallow copy ({...obj} or Object.assign({}, obj)) copies own enumerable properties one level deep — nested objects are still shared references. Deep copy (structuredClone(obj)) recursively copies all levels. JSON.parse(JSON.stringify(obj)) is a broken deep copy that loses functions, undefined, Dates (converts to string), and throws on circular references.

37. What does Object.freeze() do?

Prevents adding, deleting, or modifying properties on the object — but only one level deep. Nested objects remain mutable. This is the most common Object.freeze gotcha. React's practice of treating state as immutable is a convention, not enforced by freeze — deep immutability requires recursive freezing or a library like Immer.

38. What is optional chaining (?.)?

Allows safe property access on potentially null/undefined values. user?.profile?.address?.city short-circuits and returns undefined instead of throwing if any intermediate value is null or undefined. Combined with nullish coalescing: user?.profile?.city ?? 'Unknown' provides both safe access and a default.

Error Handling

39. What is the difference between throw new Error() and throw 'message'?

You can throw any value in JavaScript — strings, numbers, objects. But throwing an Error object is correct because Error captures a stack trace automatically. A thrown string gives you nothing to debug with. Custom errors should extend Error: class NetworkError extends Error { constructor(msg, statusCode) { super(msg); this.name = 'NetworkError'; this.statusCode = statusCode } }.

40. How does error handling work with async/await?

Rejected Promises throw inside async functions and are caught by try/catch. The catch block receives the rejection reason. Without try/catch, the rejected Promise propagates up to the caller, who must handle it. Unhandled rejections cause UnhandledPromiseRejection warnings in Node.js and will terminate the process in newer versions.

Modern JavaScript (ES6+)

41. What is destructuring and what does it enable?

Destructuring extracts values from arrays or properties from objects into named variables in a single expression. It enables function parameter destructuring (function({ name, age }) {}), swapping variables ([a, b] = [b, a]), renaming (const { name: firstName } = user), defaults (const { role = 'user' } = data), and nested extraction in one line.

42. What is the difference between spread and rest?

Same ... syntax, opposite directions. Rest collects multiple values into an array (in function parameters). Spread expands an iterable into individual elements (in function calls, array literals, object literals). A common interview confusion: function foo(...args) — rest. foo(...myArray) — spread.

43. What are generators and what makes them unique?

Functions that can pause mid-execution and resume. Defined with function*, they use yield to pause and hand back a value to the caller. Each call to .next() runs until the next yield. Generators are lazy — they produce values only when asked. They're the foundation of Redux-Saga's async control flow and can implement infinite sequences without memory issues.

44. What is the difference between Map and a plain object for key-value storage?

Map: any value as key (objects, functions, primitives), preserves insertion order, has .size, no prototype chain (no inherited key collisions), and can be iterated directly with for...of. Object: only string or symbol keys, prototype chain creates potential key collisions (toString, constructor), no guaranteed iteration order for numeric keys. Use Map when keys are dynamic or non-string.

45. What is a WeakMap and when would you use it?

A Map where keys must be objects and references are held weakly — they don't prevent the key from being garbage collected. When the key object is collected, its entry disappears automatically. Use WeakMap to associate metadata with DOM elements or objects without preventing their cleanup: const cache = new WeakMap(); cache.set(element, computedData).

Performance & Patterns

46. What is debounce?

Delays execution of a function until N milliseconds after the last call. If called repeatedly (like on every keystroke), only the final call fires after the pause. Implemented with clearTimeout + setTimeout — each new call cancels the previous timer. Use for search inputs, window resize handlers, and any UI event that fires faster than you want to react.

47. What is throttle?

Limits a function to execute at most once per N milliseconds, regardless of how many times it's called. Unlike debounce which waits for silence, throttle guarantees regular execution. Use for scroll position tracking, progress bars, and any case where you want consistent periodic updates rather than waiting for the user to stop.

48. What is memoization?

Caching the return value of a pure function based on its inputs. First call computes and caches. Subsequent calls with the same arguments return the cached result immediately. Only valid for pure functions (same inputs always produce same output). React's useMemo and useCallback are memoization hooks. The tradeoff: memory usage vs computation time.

49. What causes layout thrashing and how do you avoid it?

Alternating between DOM reads (which force the browser to calculate layout) and DOM writes (which invalidate the calculated layout) in a loop. Each read after a write forces a synchronous reflow — extremely expensive. Fix: batch all reads, then batch all writes. Libraries like FastDOM formalize this. React's virtual DOM solves it architecturally by batching all DOM mutations.

50. What is the difference between == type coercion and explicit conversion?

Coercion happens implicitly when operators or comparisons receive mismatched types. Explicit conversion is intentional: Number('42'), String(123), Boolean(0), parseInt('3.14'). Relying on implicit coercion produces code that requires the reader to memorize the coercion rules — a reliability risk. Explicit conversion makes intent clear and behavior predictable.

---

Practice tip: For each of these topics, JSPrep has dedicated question sets with output tracing, debug challenges, and theory questions. The questions link back to the concept hubs for each topic — use them when an answer above prompts a deeper question.
📚 Practice These Topics
This keyword
6–10 questions
Closure
8–12 questions
Scope
4–6 questions
Execution context
3–6 questions

Put This Into Practice

Reading articles is passive. JSPrep Pro makes you actively recall, predict output, and get AI feedback.

Start Free →Browse All Questions

Related Articles

Core Concepts
map() vs forEach() in JavaScript: Which One to Use and Why It Matters
7 min read
Core Concepts
Arrow Functions vs Regular Functions in JavaScript: 6 Key Differences
9 min read
Interview Prep
Promise.all vs allSettled vs race vs any: The Complete Comparison
9 min read