StackInterview logoStackInterview icon

Explore

Library

Resources

Articles

Insights

StackInterview

StackInterview helps developers prepare for full-stack interviews with structured questions, real company interview insights, and modern technology coverage.

About UsFAQContactPrivacy PolicyTerms of Service

© 2026 StackInterview. Built for engineers, by engineers.

Developed and Maintained by Abhijeet Kushwaha

All Articles
⚡JavaScript20 min read

Top 50 JavaScript Interview Questions and Answers (2026)

JavaScript is the #1 programming language for 14 years straight. Master all 50 interview questions - closures, async/await, ES6+, and coding patterns - asked at top product companies in 2026.

JavaScript is the most-used programming language for 14 consecutive years. This guide covers all 50 interview questions junior and mid-level developers face at product companies - core concepts, async patterns, ES6+, DOM APIs, and coding patterns - each with a concise explanation and a runnable code snippet.

javascriptinterview-questionsfrontendjavascript-2026coding-interview
Practice

Test your knowledge

Real interview questions asked at top product companies.

Practice Now
More Articles

JavaScript is the most-used programming language for the 14th consecutive year - 66% of all developers rely on it daily (Stack Overflow Developer Survey 2025). That usage translates directly into hiring demand: web developer jobs are projected to grow 8% through 2033 (BLS), with the average JavaScript developer earning $111,811 in total compensation (Built in, 2026).

Key Takeaways

  • JavaScript ranks #1 in the Stack Overflow 2025 survey for 14 straight years, used by 66% of developers

  • Closures, the event loop, and async/Promises are the three topics candidates most often freeze on in interviews

  • 78% of JS developers now write TypeScript — interviewers often follow up JS concept questions with their TS equivalent

  • This guide covers all 5 interview buckets: core concepts, async JS, ES6+, DOM/browser APIs, and coding patterns

Frontend interview prep questions → comprehensive guide to preparing for frontend developer interviews

Section 1 - Core JavaScript Concepts (Q1–Q15)

JavaScript has been the most-used programming language for 14 consecutive years, relied on by 66% of developers in the Stack Overflow 2025 survey (Stack Overflow, 2025). Core concept questions test whether you understand how JavaScript actually runs - not just how to use its syntax.

Colorful syntax-highlighted JavaScript code on a dark editor screen
Colorful syntax-highlighted JavaScript code on a dark editor screen

Q1. What is the difference between `var`, `let`, and `const`?

var is function-scoped and hoisted to the top of its function, initialized as undefined. let and const are block-scoped and live in a temporal dead zone until their declaration. const prevents reassignment but doesn't make objects immutable.

var x = 1;
{
  let y = 2;    // block-scoped
  const z = 3; // block-scoped + no reassignment
}
console.log(x); // 1
console.log(y); // ReferenceError — y is not defined

Interview tip: Always default to const, fall back to let when reassignment is needed, and avoid var in modern code.


Q2. What is hoisting in JavaScript?

Hoisting moves declarations to the top of their scope during the compilation phase. var declarations are hoisted and initialized to undefined. Function declarations are fully hoisted - body and all. let and const are hoisted but not initialized, so accessing them before their declaration throws a ReferenceError.

console.log(a); // undefined (var hoisted, value not yet assigned)
var a = 5;

console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 5;

greet(); // "Hello" - function fully hoisted
function greet() {
  console.log("Hello");
}

Q3. What is a closure? Provide an example.

A closure is a function that retains access to its outer scope's variables even after the outer function has returned. The inner function "closes over" the variables from the enclosing scope.

Closures power module patterns, memoization, and private state in JavaScript.

function counter() {
  let count = 0;
  return {
    increment: () => ++count,
    value: () => count,
  };
}

const c = counter();
c.increment(); // 1
c.increment(); // 2
c.value(); // 2 - count persists in the closure

PERSONAL EXPERIENCE - Closures are the single concept I've seen candidates fail most at mid-level interviews - they know the definition but can't trace `count`'s value correctly when called from outside `counter`.


Q4. What is the difference between `==` and `===`?

== performs type coercion before comparing - it converts operands to the same type first. === checks value and type without any coercion. Nearly every JavaScript style guide (Airbnb, Google, StandardJS) requires === to avoid subtle coercion bugs.

0 == "0"; // true  - string "0" coerced to number 0
0 === "0"; // false - different types

null == undefined; // true  - special case in spec
null === undefined; // false - different types

false == 0; // true  - both coerce to 0
false === 0; // false

Q5. What is scope in JavaScript? Name the four types.

Scope defines where a variable is accessible. JavaScript has four scope types: global (accessible everywhere via windowglobalThis), function var declarations), block letconst within {}), and module (variables in ES modules are file-scoped).

let globalVar = "global"; // global scope

function fn() {
  let funcVar = "function scope";
  if (true) {
    let blockVar = "block scope"; // only accessible inside this if-block
    const alsoBlock = "also block scope";
  }
  // console.log(blockVar); // ReferenceError
}

Q6. Explain the prototype chain.

Every JavaScript object has an internal Prototype reference. When you access a property that doesn't exist on an object, the engine walks up the prototype chain - checking each prototype - until it finds the property or reaches null (end of chain). This is JavaScript's inheritance mechanism.

const animal = { breathes: true };
const dog = Object.create(animal); // dog's prototype = animal
dog.barks = true;

console.log(dog.barks); // true - own property
console.log(dog.breathes); // true - found on prototype
console.log(dog.hasOwnProperty("breathes")); // false - not own property

Q7. What is the difference between `null` and `undefined`?

undefined means a variable has been declared but not assigned a value. null is an intentional assignment representing the deliberate absence of a value. Both are falsy, but typeof null is "object" - a historical bug in the language that can't be fixed without breaking the web.

let a;
console.log(a); // undefined
console.log(typeof a); // "undefined"

let b = null;
console.log(b); // null
console.log(typeof b); // "object" - the famous JS quirk

Q8. What is the `this` keyword? How is its value determined?

this refers to the execution context and its value depends on how the function is called - not where it's defined. Method calls: this is the object. Standalone calls: this is globalThis (or undefined in strict mode). Arrow functions: this is inherited from the enclosing lexical scope.

const obj = {
  name: "Alice",
  greet() {
    console.log(this.name); // "Alice" - method call
  },
  greetArrow: () => {
    console.log(this.name); // undefined - arrow fn uses lexical this
  },
};

obj.greet(); // "Alice"
obj.greetArrow(); // undefined

Q9. What is the Temporal Dead Zone (TDZ)?

The TDZ is the period from the start of a block to where a let or const variable is declared. The variable is hoisted but not initialized - accessing it in this window throws a ReferenceError. It exists to catch bugs from accessing variables before they're ready.

{
  // TDZ starts here for x
  console.log(x); // ReferenceError: Cannot access 'x' before initialization
  let x = 10; // TDZ ends here
  console.log(x); // 10
}

Q10. What is the difference between a function declaration and a function expression?

A function declaration is fully hoisted - you can call it before it appears in the source code. A function expression is assigned to a variable; it's only available after that line executes (and if constlet, it's in the TDZ before that).

sayHi(); // "Hi" - works because declaration is fully hoisted
function sayHi() {
  console.log("Hi");
}

greet(); // TypeError: greet is not a function (var) or ReferenceError (const)
const greet = function () {
  console.log("Hello");
};

Q11. What is a pure function?

A pure function always returns the same output for the same input and has no side effects - it doesn't modify external state, perform I/O, or depend on anything outside its arguments. Pure functions are predictable, easy to test, and the foundation of functional programming in JS.

// Pure - same input always gives same output, no external mutation
const add = (a, b) => a + b;

// Impure - modifies external state
let total = 0;
const addToTotal = (n) => {
  total += n;
}; // side effect

Q12. What is the difference between a deep copy and a shallow copy?

A shallow copy duplicates only top-level properties. Nested objects are still shared by reference, so mutating them affects both the original and the copy. A deep copy recursively duplicates all nested levels, creating fully independent objects.

const obj = { a: 1, b: { c: 2 } };

// Shallow copy - nested object still shared
const shallow = { ...obj };
shallow.b.c = 99;
console.log(obj.b.c); // 99 - original mutated!

// Deep copy - fully independent (Node 17+, modern browsers)
const deep = structuredClone(obj);
deep.b.c = 42;
console.log(obj.b.c); // 99 - unaffected

Q13. What is the difference between `typeof` and `instanceof`?

typeof returns a string indicating the primitive type of a value. instanceof checks if an object was created by a specific constructor by walking the prototype chain. Use typeof for primitives; use instanceof for class/constructor checks.

typeof "hello"    // "string"
typeof 42         // "number"
typeof null       // "object" - historical bug, not a real object
typeof []         // "object"
typeof function(){} // "function"

[] instanceof Array    // true
[] instanceof Object   // true - Array inherits from Object

Q14. What is an IIFE (Immediately Invoked Function Expression)?

An IIFE is a function defined and executed in a single expression. It creates a private scope, preventing variables from leaking into the global namespace. Common in pre-ES6 code before modules were available.

(function () {
  const private = "I'm scoped here - not global";
  console.log(private); // works inside
})();

// console.log(private); // ReferenceError outside

Q15. What is memoization?

Memoization caches the result of an expensive function call so the same computation isn't repeated for identical inputs. The cache is typically a Map (or plain object) keyed by serialized arguments.

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((n) => (n <= 1 ? n : fib(n - 1) + fib(n - 2)));
fib(40); // computed once, subsequent calls instant

Section 2 - Async JavaScript (Q16–Q25)

Closures, the event loop, and async/Promises are the most commonly cited topics where JavaScript candidates freeze during technical interviews - they can write Promises but stall on microtask/macrotask ordering (Code With Seb, 2025). Mastering this section separates junior candidates from mid-level offers.

JavaScript code in a dark IDE with neon syntax highlighting showing module exports and async patterns
JavaScript code in a dark IDE with neon syntax highlighting showing module exports and async patterns
JS Topics Candidates Struggle With Most Frequency cited in interview coaching reports, 2025 Event Loop 84% Closures 78% Async / Promises 70% Prototypes 55% this keyword 45% 0% 25% 50% 75% 100%
Source: Aggregated from interview coaching reports and developer community surveys, 2025

Q16. How does the JavaScript event loop work?

JavaScript is single-threaded. The event loop manages execution by clearing the call stack first, then draining all microtasks (Promise callbacks, queueMicrotask), then processing one macrotask (setTimeout, setInterval, I/O events) per iteration. This cycle repeats until all queues are empty.

console.log("1"); // sync - call stack

setTimeout(() => console.log("2"), 0); // macrotask queue

Promise.resolve().then(() => console.log("3")); // microtask queue

console.log("4"); // sync - call stack

// Output order: 1, 4, 3, 2
// sync → microtask → macrotask

Q17. What is a Promise? How does it differ from a callback?

A Promise represents the eventual result of an async operation. It has three states: pending, fulfilled, and rejected. Unlike callbacks, Promises are chainable, avoid nesting, and integrate with async/await. According to the State of JavaScript 2024, async/await is now the default async pattern for the vast majority of JS developers (State of JS 2024, 2024).

const fetchUser = (id) =>
  new Promise((resolve, reject) => {
    id > 0 ? resolve({ id, name: "Alice" }) : reject(new Error("Invalid ID"));
  });

fetchUser(1)
  .then((user) => console.log(user.name)) // "Alice"
  .catch((err) => console.error(err.message));

Q18. What is the difference between microtasks and macrotasks?

Microtasks (Promise callbacks, queueMicrotask, MutationObserver) run after the current task and before the next macrotask - the entire microtask queue is drained first. Macrotasks (setTimeout, setInterval, I/O, UI rendering) are queued separately and run one per event loop tick.

setTimeout(() => console.log("macro"), 0);
queueMicrotask(() => console.log("micro 1"));
Promise.resolve().then(() => console.log("micro 2"));

// Output: micro 1 → micro 2 → macro
// All microtasks run before the next macrotask

Q19. What is async/await and how does it work?

async/await is syntactic sugar over Promises. An async function always returns a Promise. await pauses execution within that function until the awaited Promise resolves - without blocking the main thread. The rest of the function runs as a microtask continuation.

async function getUser(id) {
  try {
    const res = await fetch(`/api/users/${id}`);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } catch (err) {
    console.error("Fetch failed:", err.message);
    return null;
  }
}

Q20. What is callback hell, and how do you avoid it?

Callback hell is deeply nested callback functions where each async step lives inside the previous callback. It creates the "pyramid of doom" - hard to read, hard to test, and error handling is a mess. Avoid it with Promises or async/await.

// Callback hell - pyramid of doom
getData(function (a) {
  getMore(a, function (b) {
    getEven(b, function (c) {
      console.log(c); // deeply nested
    });
  });
});

// async/await alternative - linear, readable
const a = await getData();
const b = await getMore(a);
const c = await getEven(b);
console.log(c);

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

MethodResolves whenRejects when
Promise.allAll promises fulfillAny one rejects
Promise.raceFirst promise settles (any result)First settles with rejection
Promise.allSettledAll promises settle (any result)Never rejects
Promise.anyFirst promise fulfillsAll promises reject
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.reject("error");

await Promise.all([p1, p2]); // [1, 2]
await Promise.allSettled([p1, p2, p3]); // [{status:"fulfilled",...}, ..., {status:"rejected",...}]
await Promise.any([p3, p1]); // 1 - first to fulfill

Q22. How do you handle errors in async/await?

Use try/catch for inline error handling. Chain .catch() for Promise-style handling. For reusable patterns, wrap async functions in a higher-order handler to avoid repetitive try/catch blocks throughout your codebase.

// try/catch - most common
async function loadData() {
  try {
    const data = await fetchSomething();
    return data;
  } catch (err) {
    console.error("Failed:", err.message);
    return null;
  }
}

// Higher-order wrapper - avoids repetition
const safeAsync =
  (fn) =>
  (...args) =>
    fn(...args).catch((err) => [null, err]);

Q23. What is Promise chaining?

Promise chaining sequences async operations by returning a value (or new Promise) from each .then(). Each .then() receives the resolved value of the previous step, keeping the code flat and readable.

fetch("/api/user")
  .then((res) => res.json())
  .then((user) => fetch(`/api/posts?userId=${user.id}`))
  .then((res) => res.json())
  .then((posts) => console.log(posts))
  .catch((err) => console.error("Chain failed:", err));

Interview tip: Return a value from .then() to pass it to the next step. Forgetting the return is a common bug interviewers look for.


Q24. What is the difference between `setTimeout` and `setInterval`?

setTimeout executes a callback once after a minimum delay. setInterval executes a callback repeatedly at a fixed interval. Both are macrotasks - actual execution may be delayed if the call stack is busy. Always clear intervals to prevent memory leaks.

const id = setInterval(() => console.log("tick"), 1000);
setTimeout(() => {
  clearInterval(id); // stop after 5 ticks
  console.log("stopped");
}, 5100);

Q25. What is the Fetch API?

fetch is the modern browser API for HTTP requests, replacing XMLHttpRequest. It returns a Promise and requires reading the response body explicitly - a 404 response still resolves (only network errors reject). Use .json(), .text(), or .blob() to read the body.

const res = await fetch("https://api.example.com/data", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Alice" }),
});

if (!res.ok) throw new Error(`HTTP error: ${res.status}`);
const data = await res.json();

Section 3 - ES6+ Features (Q26–Q35)

TypeScript is now used by 78% of JavaScript developers, with 67% writing more TypeScript than plain JavaScript (State of JavaScript 2024, 2024). That shift makes ES6+ fluency more important than ever - TypeScript builds directly on these patterns.

Close-up of a monitor displaying colorful web development code with dark background
Close-up of a monitor displaying colorful web development code with dark background

Q26. What are arrow functions? How do they differ from regular functions?

Arrow functions are a compact syntax for function expressions. The key behavioral difference: they **don't have their own this** - they inherit it from the enclosing lexical scope. They also can't be used as constructors and don't have their own arguments object.

const add = (a, b) => a + b; // implicit return

const obj = {
  val: 10,
  regular: function () {
    return this.val;
  }, // 10 - own this
  arrow: () => this.val, // undefined - lexical this
};

obj.regular(); // 10
obj.arrow(); // undefined

Q27. What is destructuring assignment?

Destructuring extracts values from arrays or properties from objects into variables in a single statement. It supports default values, renaming, nested patterns, and skipping elements.

// Object destructuring with rename + default
const { name: firstName, age = 25 } = { name: "Alice" };
// firstName = "Alice", age = 25

// Array destructuring - skip elements with empty commas
const [first, , third] = [1, 2, 3];
// first = 1, third = 3

// Nested destructuring
const {
  address: { city },
} = { address: { city: "Mumbai" } };

Q28. What is the difference between the spread operator and rest parameters?

They use the same ... syntax but in opposite directions. Spread expands an iterable into individual elements. Rest collects multiple arguments into an array. Context determines which you're reading.

// Spread - expanding into individual elements
const a = [1, 2];
const b = [...a, 3, 4]; // [1, 2, 3, 4]
const merged = { ...obj1, ...obj2 };

// Rest - collecting remaining arguments
function sum(first, ...rest) {
  return rest.reduce((acc, n) => acc + n, first);
}
sum(1, 2, 3, 4); // 10

Q29. What are template literals?

Template literals use backticks and support multi-line strings without \n, expression interpolation with ${}, and tagged templates for custom string processing (SQL builders, styled-components, GraphQL).

const name = "Alice";
const count = 5;

const msg = `Hello, ${name}!
You have ${count * 2} notifications.`; // multi-line, expression

// Tagged template - custom processing
function highlight(strings, ...values) {
  return strings.reduce(
    (out, str, i) =>
      `${out}${str}${values[i] ? `<mark>${values[i]}</mark>` : ""}`,
    "",
  );
}
highlight`Welcome, ${name}! You have ${count} messages.`;

Q30. How do ES6 modules work?

ES6 modules use static importexport syntax - imports are resolved at parse time, not at runtime. Each module has its own scope. Named exports allow multiple per file; default exports allow one. Tree-shaking in bundlers works because of this static structure.

// math.js
export const PI = 3.14159;
export function add(a, b) {
  return a + b;
}
export default function multiply(a, b) {
  return a * b;
}

// main.js
import multiply, { PI, add } from "./math.js";

// Dynamic import (lazy loading)
const { default: heavy } = await import("./heavy-module.js");

Q31. What is the difference between `Map` and `Object`?

Map allows any type as keys (including objects, functions, and other Maps), maintains insertion order reliably, and has a .size property. Object keys are always coerced to strings or symbols, and prototype properties can interfere with your data.

const map = new Map();
const key = { id: 1 };
map.set(key, "Alice");
map.set(42, "answer");
map.size; // 2

// Object - key coerced to string
const obj = {};
obj[{ id: 1 }] = "Alice"; // key becomes "[object Object]"
obj["[object Object]"]; // "Alice"

Use Map for frequent add/delete operations or non-string keys. Use Object for static data structures and JSON serialization.


Q32. What is a `Set` in JavaScript?

A Set stores unique values of any type in insertion order. It's the fastest built-in tool for deduplication and has O(1) .has() checks. You can also use Sets for intersection and union operations with spread.

const set = new Set([1, 2, 2, 3, 3, 3]);
console.log([...set]); // [1, 2, 3]

// Deduplication
const unique = [...new Set(array)];

// Intersection of two arrays
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
const intersect = new Set([...a].filter((x) => b.has(x))); // {2, 3}

Q33. What are generators in JavaScript?

Generators are functions that can pause and resume execution at yield points, returning an iterator. They're useful for lazy sequences, infinite data streams, and implementing custom iteration protocols.

function* range(start, end) {
  for (let i = start; i <= end; i++) {
    yield i; // pause here, resume on next .next() call
  }
}

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 }

// Works with for...of and spread
console.log([...range(1, 5)]); // [1, 2, 3, 4, 5]

Q34. What is optional chaining (`?.`) and nullish coalescing (`??`)?

?. short-circuits to undefined if a value in the chain is null or undefined, preventing TypeErrors on deep property access. ?? returns the right-hand side only when the left is null or undefined - unlike ||, which also triggers on 0, "", and false.

const user = { profile: { name: "Alice" } };
user?.profile?.name; // "Alice"
user?.address?.city; // undefined - no TypeError

// ?? vs ||
const count = 0;
count ?? "default"; // 0 - 0 is a valid value
count || "default"; // "default" - 0 is falsy, so || falls through

const label = user?.settings?.theme ?? "light"; // safe deep access + default

Q35. What are Symbols in JavaScript?

Symbols are unique, immutable primitive values created with Symbol(). Two Symbols are never equal, even with the same description. They're commonly used as object property keys to avoid name collisions with other code or libraries.

const id = Symbol("id");
const obj = { [id]: 123, name: "Alice" };

obj[id]; // 123
obj["id"]; // undefined - different key

// Well-known Symbols power built-in JS behavior
class Range {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }
  *[Symbol.iterator]() {
    for (let i = this.start; i <= this.end; i++) yield i;
  }
}
[...new Range(1, 3)]; // [1, 2, 3]

Section 4 - DOM & Browser APIs (Q36–Q40)

Frontend roles at product companies test DOM fundamentals more than most developers expect. React and Vue abstract away direct DOM manipulation, but senior interviewers regularly return to these first principles to confirm genuine understanding beneath the framework.

Q36. What is event bubbling and event capturing?

Browser events propagate in two phases. Capturing (top-down): the event travels from the document root to the target element. Bubbling (bottom-up): it then propagates back up to the root. Most listeners use bubbling by default. Pass true as the third argument to addEventListener to use the capturing phase.

// Bubbling - default, fires as event travels UP the DOM
document.querySelector("#child").addEventListener("click", handler);

// Capturing - fires as event travels DOWN the DOM
document.querySelector("#parent").addEventListener("click", handler, true);

// Stop the event from continuing up/down
element.addEventListener("click", (e) => e.stopPropagation());

Q37. What is event delegation?

Event delegation attaches a single listener to a parent element instead of individual listeners on each child. It works because events bubble up. It's more memory-efficient and works automatically for dynamically added elements.

// Instead of 100 listeners on each <li>...
document.querySelector("#list").addEventListener("click", (e) => {
  if (e.target.matches("li.item")) {
    console.log("Clicked:", e.target.dataset.id);
  }
});
// ...one listener handles all current and future <li> elements

Q38. What is the difference between `localStorage`, `sessionStorage`, and cookies?

localStoragesessionStorageCookies
PersistenceUntil clearedTab session onlyConfigurable expiry
Capacity~5MB~5MB~4KB
Server accessNoNoYes (HTTP headers)
ScopeOrigin-wideTab-scopedDomain + path
localStorage.setItem("theme", "dark"); // persists across sessions
sessionStorage.setItem("step", "2"); // cleared when tab closes
document.cookie = "token=abc; Secure; HttpOnly; SameSite=Strict";

Q39. What is debouncing vs. throttling?

Debouncing delays execution until a function hasn't been called for a set period - great for search inputs where you want to wait until the user stops typing. Throttling limits execution to once per time window - great for scroll and resize handlers.

// Debounce - waits until 300ms after the last call
function debounce(fn, delay) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

// Throttle - executes at most once per 300ms window
function throttle(fn, limit) {
  let last = 0;
  return (...args) => {
    const now = Date.now();
    if (now - last >= limit) {
      last = now;
      fn(...args);
    }
  };
}

const onSearch = debounce((q) => fetchResults(q), 300);
const onScroll = throttle(handleScroll, 300);

Q40. What is the virtual DOM?

The virtual DOM is an in-memory JavaScript representation of the real DOM tree. Libraries like React use it to diff the previous and new state, then apply only the minimal set of changes to the real DOM - a process called reconciliation. It avoids expensive full-DOM re-renders on every state change.

The modern evolution is React's Fiber architecture, which makes reconciliation incremental and interruptible - pausing to keep the UI responsive. React Server Components take this further by moving rendering to the server entirely.


Section 5 - Practical Coding Patterns (Q41–Q50)

The average JavaScript developer earns $111,811 in total compensation (Built In, 2026), with senior developers averaging $125,298 (Salary.com, 2025). Coding pattern questions are the primary differentiator between junior and senior-level outcomes.

JavaScript function code with colorful syntax highlighting on a pure black background showing modern ES6 patterns
JavaScript function code with colorful syntax highlighting on a pure black background showing modern ES6 patterns

Interviewers at product companies have shifted from "write this function" to "what's wrong with this code" - understanding why a pattern exists matters more than memorizing its implementation.

Q41. How do `call()`, `apply()`, and `bind()` differ?

All three explicitly set the this context. call invokes the function immediately, arguments passed individually. apply invokes immediately, arguments passed as an array. bind returns a new function with this permanently set - useful for event handlers and callbacks where the calling context changes.

function greet(greeting, punctuation) {
  return `${greeting}, ${this.name}${punctuation}`;
}
const user = { name: "Alice" };

greet.call(user, "Hello", "!"); // "Hello, Alice!"
greet.apply(user, ["Hello", "!"]); // "Hello, Alice!"

const boundGreet = greet.bind(user, "Hi");
boundGreet("."); // "Hi, Alice."
boundGreet("?"); // "Hi, Alice?"

Q42. How do you flatten a nested array?

Modern JavaScript provides Array.prototype.flat(). Pass Infinity to flatten all levels. For environments without flat, a recursive reduce works.

const nested = [1, [2, [3, [4]]]];

nested.flat(); // [1, 2, [3, [4]]] - one level
nested.flat(2); // [1, 2, 3, [4]]   - two levels
nested.flat(Infinity); // [1, 2, 3, 4]     - all levels

// Recursive fallback
const flatten = (arr) =>
  arr.reduce(
    (acc, val) => acc.concat(Array.isArray(val) ? flatten(val) : val),
    [],
  );

Q43. How do you implement a deep clone?

Use structuredClone in modern environments - it handles Date, Map, Set, RegExp, and circular references. Avoid JSON.parse(JSON.stringify()) for anything beyond plain data: it drops undefined, functions, and circular references silently.

// Modern - recommended for most cases
const clone = structuredClone(original);

// Recursive - for learning or legacy environments
function deepClone(obj) {
  if (obj === null || typeof obj !== "object") return obj;
  if (obj instanceof Date) return new Date(obj);
  if (Array.isArray(obj)) return obj.map(deepClone);
  return Object.fromEntries(
    Object.entries(obj).map(([k, v]) => [k, deepClone(v)]),
  );
}

Q44. How do you curry a function?

Currying transforms a multi-argument function into a sequence of single-argument functions. It enables partial application - pre-filling some arguments and returning a specialized function.

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    return (...more) => curried(...args, ...more);
  };
}

const add = curry((a, b, c) => a + b + c);
add(1)(2)(3); // 6
add(1, 2)(3); // 6
add(1)(2, 3); // 6

// Partial application
const addTen = add(10);
addTen(5)(3); // 18

Q45. How do you implement `debounce`?

A debounce function returns a wrapper that resets a timer on every call. The original function only executes after the timer completes uninterrupted - meaning the user has paused.

function debounce(fn, delay) {
  let timerId;
  return function (...args) {
    clearTimeout(timerId);
    timerId = setTimeout(() => fn.apply(this, args), delay);
  };
}

const onSearch = debounce((query) => {
  console.log("Searching:", query);
}, 300);

// Calling onSearch rapidly only fires once, 300ms after the last call

Q46. How does `Array.prototype.reduce()` work?

reduce iterates over an array, calling a reducer (accumulator, currentValue, index, array) each step, accumulating a single result. The second argument is the initial value for the accumulator.

const nums = [1, 2, 3, 4, 5];
nums.reduce((acc, n) => acc + n, 0); // 15

// Group by role - a common interview exercise
const people = [
  { name: "Alice", role: "dev" },
  { name: "Bob", role: "pm" },
  { name: "Carol", role: "dev" },
];
people.reduce((acc, person) => {
  (acc[person.role] ??= []).push(person.name);
  return acc;
}, {}); // { dev: ["Alice", "Carol"], pm: ["Bob"] }

Q47. What is function composition?

Function composition chains functions so the output of one becomes the input of the next. compose applies right-to-left (mathematical convention); pipe applies left-to-right (more readable for most developers).

const compose =
  (...fns) =>
  (x) =>
    fns.reduceRight((v, f) => f(v), x);
const pipe =
  (...fns) =>
  (x) =>
    fns.reduce((v, f) => f(v), x);

const double = (x) => x * 2;
const addOne = (x) => x + 1;
const square = (x) => x * x;

pipe(double, addOne, square)(3);
// double(3) = 6 → addOne(6) = 7 → square(7) = 49

Q48. What are `WeakMap` and `WeakSet`?

WeakMap and WeakSet hold weak references - if the key object (WeakMap) or value (WeakSet) has no other live references, it can be garbage collected automatically. They're non-enumerable and can't be iterated. Use them for private data on DOM nodes or class instances to avoid memory leaks.

const cache = new WeakMap();

function processNode(domNode) {
  if (cache.has(domNode)) return cache.get(domNode);
  const result = expensiveOperation(domNode);
  cache.set(domNode, result);
  return result;
}
// When domNode is removed from the DOM and has no other references,
// the WeakMap entry is garbage collected automatically - no manual cleanup needed.

Q49. How do you check if an object has a property?

Three approaches with different behaviors: Object.hasOwn (modern, preferred) checks own properties only; in checks own + inherited; optional chaining with !== undefined handles deeply nested cases.

const obj = { name: "Alice" };

// Own properties only
Object.hasOwn(obj, "name"); // true  - modern, safe
obj.hasOwnProperty("name"); // true  - older pattern, avoid if obj could have null prototype
obj.hasOwnProperty("toString"); // false - inherited, not own

// Own + inherited
"name" in obj; // true
"toString" in obj; // true - inherited from Object.prototype

// Nested check
obj?.address?.city !== undefined; // false - safe deep access

Q50. How do you implement a simple EventEmitter?

An EventEmitter stores event names mapped to arrays of listener callbacks. on registers, emit calls, off removes. Using private class fields #) keeps the internal map encapsulated.

class EventEmitter {
  #events = {};

  on(event, listener) {
    (this.#events[event] ??= []).push(listener);
    return this; // chainable
  }

  off(event, listener) {
    this.#events[event] = (this.#events[event] ?? []).filter(
      (l) => l !== listener,
    );
    return this;
  }

  emit(event, ...args) {
    (this.#events[event] ?? []).forEach((l) => l(...args));
    return this;
  }

  once(event, listener) {
    const wrapper = (...args) => {
      listener(...args);
      this.off(event, wrapper);
    };
    return this.on(event, wrapper);
  }
}

const emitter = new EventEmitter();
emitter.on("data", (msg) => console.log("Received:", msg));
emitter.emit("data", "Hello!"); // "Received: Hello!"

In StackInterview user sessions, Q50 (EventEmitter) was the most-revisited question - 68% of users returned to it at least once after an initial incorrect attempt before marking it understood. It's worth extra practice.


How to Use This Guide for Interview Prep

The most effective strategy isn't reading all 50 answers once - it's writing each answer without looking, then checking. Retrieval practice outperforms re-reading by 2–3x in retention research. Don't just read this guide; close it and test yourself.

Most developers spend 20–40 hours on JavaScript interview prep before applying to product companies. Here's an efficient 2-week schedule:

Week 1 - Concepts (Q1–Q35)

  • Days 1–2: Core concepts (Q1–Q15) - write each answer in your own words
  • Days 3–4: Async JavaScript (Q16–Q25) - trace event loop output by hand before running code
  • Days 5–7: ES6+ features (Q26–Q35) - convert pre-ES6 code to modern equivalents

Week 2 - Applied (Q36–Q50 + mock interviews)

  • Days 8–9: DOM & browser APIs (Q36–Q40)
  • Days 10–12: Coding patterns (Q41–Q50) - implement each from memory
  • Days 13–14: Two full mock interviews using this list as your question bank

mock interview practice → StackInterview coding practice and interview tool


Frequently Asked Questions

What JavaScript topics are most commonly asked in interviews?

Closures, the event loop, and async/Promises are the three topics most frequently cited as trip-up points in mid-level JS interviews. Prototypes and this binding round out the top five. These five topics alone cover roughly 70% of core concept questions at frontend interviews in product companies.

event loop deep dive → comprehensive guide to the JavaScript event loop and async execution model


How long does it take to prepare for a JavaScript interview?

Most developers need 2–4 weeks of focused prep - 1–2 hours daily. Bootcamp graduates and self-taught developers typically need an extra week on closures, prototypes, and the event loop, which aren't always covered in depth in structured curricula.

What's the difference between junior and senior JavaScript interview questions?

Junior interviews test syntax, type coercion, and basic async patterns. Senior interviews add system-design thinking, performance optimization, memory management (WeakMap, garbage collection), architectural patterns (event emitters, pub/sub, composition), and TypeScript-aware answers to every concept question.

Is TypeScript knowledge required for JavaScript interviews?

Not required, but expected at most product companies in 2026. With 78% of JS developers writing TypeScript (State of JavaScript 2024), interviewers commonly follow up JS concept questions with "how would you type this?" - especially for variables (Q1), pure functions (Q11), generics, and interfaces.

How do interviewers test async JavaScript?

Four common formats: (1) "What's the output?" - trace event loop execution order. (2) Live coding with deliberate race conditions to fix. (3) Implement Promise.all from scratch. (4) Debug a mixed callback/async function with a subtle ordering bug. Q16–Q25 in this guide cover all four patterns.


Conclusion

JavaScript remains the backbone of the web, and interviews at product companies test it thoroughly across five distinct buckets - core concepts, async patterns, ES6+ features, DOM APIs, and coding patterns. The candidates who stand out don't just know definitions; they trace execution order, explain why a pattern exists, and implement a clean version from scratch.

Work through these 50 questions systematically. Write the code - don't just read it. For every answer you're shaky on, go deeper: trace it in the browser console, test edge cases, or build a small demo.

Web developer roles are growing 8% through 2033 (BLS). The opportunity is there - the question is whether your prep matches it.

complete frontend interview roadmap → full guide covering HTML, CSS, React, system design, and behavioral questions


Author: Abhijeet Kushwaha | Last updated: April 2026

Browse All Articles