Top JavaScript Interview Questions for 5+ Years Experience (2026)

๐Ÿ‘๏ธ 203 Views
|
๐Ÿ“… Aug 10, 2026
|
โฑ๏ธ 19 min read
Top JavaScript Interview Questions for 5+ Years Experience (2026)

JavaScript interviews at the 5+ year level are not about knowing the syntax โ€” they are about understanding why JavaScript behaves the way it does. The event loop, closure mechanics, prototype chains, this binding, memory management โ€” these are the topics that separate a developer who has used JavaScript for five years from one who truly understands it. This guide covers the questions most commonly asked to senior JavaScript developers with detailed, production-level answers.

๐Ÿ“‹ Jump to a Question

  1. How does the JavaScript Event Loop work?
  2. What are closures and how do you use them in production?
  3. Explain the prototype chain and prototypal inheritance
  4. var vs let vs const โ€” hoisting and Temporal Dead Zone
  5. How does the this keyword work in different contexts?
  6. call(), apply(), and bind() โ€” differences and when to use each
  7. Implement debounce and throttle from scratch
  8. How do Promises work under the hood?
  9. WeakMap and WeakSet โ€” when and why?
  10. How does the V8 engine and JIT compilation work?
  11. Common JavaScript memory leaks and how to detect them
  12. Generators and Iterators โ€” practical use cases
  13. Proxy and Reflect API โ€” what are they and when to use them?
  14. CommonJS vs ES Modules โ€” differences and interoperability
  15. Web Workers โ€” true parallelism in JavaScript

1. How Does the JavaScript Event Loop Work?

This is the most important concept to understand deeply at the senior level. JavaScript is single-threaded โ€” only one piece of code runs at a time. The event loop is what makes asynchronous behaviour possible without threads.

// JavaScript runtime has:
// - Call Stack      โ†’ where function execution happens (LIFO)
// - Web APIs        โ†’ browser-provided: setTimeout, fetch, DOM events
// - Callback Queue  โ†’ also called Macrotask queue (setTimeout, setInterval callbacks)
// - Microtask Queue โ†’ Promise .then(), queueMicrotask(), MutationObserver
// - Event Loop      โ†’ continuously checks: if stack empty, move tasks to stack

console.log("1");                          // Call Stack

setTimeout(() => console.log("2"), 0);    // Web API โ†’ Callback Queue (macrotask)

Promise.resolve().then(() => console.log("3")); // Microtask Queue

console.log("4");                          // Call Stack

// Output: 1, 4, 3, 2
// Why? Execution order:
// 1. Synchronous code runs first: "1", "4"
// 2. Microtasks drain completely before any macrotask: "3"
// 3. Macrotasks run one at a time: "2"
// Microtasks always run before the next macrotask
// Even if a macrotask was queued first

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

Promise.resolve()
  .then(() => console.log("promise 1"))         // microtask
  .then(() => console.log("promise 2"));        // microtask

// Output: promise 1, promise 2, timeout
// Both microtasks run before the setTimeout callback

Senior tip: Explain the distinction between the microtask queue and the macrotask queue โ€” most candidates only know about "the callback queue." Knowing that Promises use the microtask queue which drains completely before any macrotask shows genuine depth of understanding.

2. What Are Closures and How Do You Use Them in Production?

A closure is a function that retains access to its outer scope even after that outer function has returned. Every function in JavaScript is a closure โ€” but understanding when and how they capture variables is what matters.

// Basic closure โ€” inner function captures outer variable
function makeCounter() {
  let count = 0; // captured by the returned function

  return function() {
    count++;
    return count;
  };
}

const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count is private โ€” cannot be accessed from outside
// Real production use case 1 โ€” module pattern (private state)
const userStore = (() => {
  let users = []; // private โ€” not accessible outside

  return {
    add(user)       { users.push(user); },
    remove(id)      { users = users.filter(u => u.id !== id); },
    getAll()        { return [...users]; }, // return copy, not reference
    count()         { return users.length; }
  };
})();

userStore.add({ id: 1, name: "Randhir" });
userStore.count(); // 1
// users array is completely private

// Real production use case 2 โ€” memoization
function memoize(fn) {
  const cache = new Map();

  return function(...args) {
    const key = JSON.stringify(args);

    if (cache.has(key)) {
      console.log("From cache");
      return cache.get(key);
    }

    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

const expensiveCalculation = memoize((n) => {
  // imagine this takes 2 seconds
  return n * n;
});

expensiveCalculation(10); // calculates โ€” 100
expensiveCalculation(10); // from cache โ€” 100 (instant)
// Classic closure trap โ€” var in loops
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Output: 3, 3, 3 โ€” not 0, 1, 2
// All three callbacks share the same 'i' reference

// Fix 1 โ€” use let (block-scoped, new binding per iteration)
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Output: 0, 1, 2 โœ…

// Fix 2 โ€” IIFE to capture value
for (var i = 0; i < 3; i++) {
  ((j) => setTimeout(() => console.log(j), 100))(i);
}
// Output: 0, 1, 2 โœ…

3. Explain the Prototype Chain and Prototypal Inheritance

JavaScript does not have classes in the traditional sense โ€” it has prototype-based inheritance. The class syntax introduced in ES6 is syntactic sugar over prototypes.

// Every object has a hidden [[Prototype]] property
// Accessed via __proto__ or Object.getPrototypeOf()

const animal = {
  breathe() { console.log("breathing"); }
};

const dog = Object.create(animal); // dog's prototype is animal
dog.bark = function() { console.log("woof"); };

dog.bark();    // found on dog itself
dog.breathe(); // not on dog โ†’ walks up prototype chain โ†’ found on animal
dog.toString();// not on dog, not on animal โ†’ found on Object.prototype

// Prototype chain:
// dog โ†’ animal โ†’ Object.prototype โ†’ null
// Constructor functions (pre-ES6)
function Person(name, age) {
  this.name = name;
  this.age  = age;
}

// Methods on prototype โ€” shared across all instances (not duplicated)
Person.prototype.greet = function() {
  return `Hi, I'm ${this.name}`;
};

const randhir = new Person("Randhir", 25);
randhir.greet(); // "Hi, I'm Randhir"

// ES6 class โ€” same thing under the hood
class Person {
  constructor(name, age) {
    this.name = name;
    this.age  = age;
  }

  greet() { // added to Person.prototype
    return `Hi, I'm ${this.name}`;
  }
}

// Inheritance
class Developer extends Person {
  constructor(name, age, language) {
    super(name, age); // calls Person constructor
    this.language = language;
  }

  introduce() {
    return `${this.greet()}, I code in ${this.language}`;
  }
}

const dev = new Developer("Randhir", 25, "JavaScript");
dev.introduce();
// Prototype chain: dev โ†’ Developer.prototype โ†’ Person.prototype โ†’ Object.prototype

4. var vs let vs const โ€” Hoisting and Temporal Dead Zone

// Hoisting โ€” declarations are moved to the top of their scope during compilation
// var is hoisted AND initialized to undefined
// let and const are hoisted but NOT initialized (Temporal Dead Zone)

// var hoisting
console.log(x); // undefined (not error โ€” hoisted and initialized)
var x = 5;
console.log(x); // 5

// let/const Temporal Dead Zone (TDZ)
console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 10;
// y exists in scope (hoisted) but is in the TDZ until the declaration line

// Function declarations are fully hoisted
greet(); // "Hello" โ€” works because function is fully hoisted
function greet() { console.log("Hello"); }

// Function expressions are NOT fully hoisted
sayHi(); // TypeError: sayHi is not a function
var sayHi = function() { console.log("Hi"); };
// var sayHi is hoisted as undefined, so sayHi() is undefined()
// var is function-scoped
function example() {
  if (true) {
    var x = 10; // function-scoped โ€” available outside the if block
  }
  console.log(x); // 10 โ€” no error
}

// let/const are block-scoped
function example() {
  if (true) {
    let y = 10; // block-scoped โ€” only inside the if block
  }
  console.log(y); // ReferenceError: y is not defined
}

// const โ€” cannot be reassigned, but object/array contents CAN change
const obj = { name: "Randhir" };
obj.name = "Priya";    // โœ… works โ€” mutating the object
obj = { name: "Amit" }; // โŒ TypeError โ€” reassigning the binding

5. How Does the this Keyword Work in Different Contexts?

this is one of the most misunderstood concepts in JavaScript. Its value depends entirely on how a function is called, not where it is defined โ€” with one exception: arrow functions.

// 1. Global context โ€” this is the global object (window in browser)
console.log(this); // window (browser) or {} (Node.js strict mode)

// 2. Regular function โ€” this depends on the caller
function show() {
  console.log(this);
}
show();        // window (non-strict) or undefined (strict mode)

// 3. Method call โ€” this is the object before the dot
const user = {
  name: "Randhir",
  greet() {
    console.log(this.name); // "Randhir" โ€” this = user
  }
};
user.greet(); // this = user โœ…

// But if you detach the method:
const greet = user.greet;
greet(); // undefined โ€” this = window/undefined (method lost its context)

// 4. Constructor โ€” this is the new object being created
function Person(name) {
  this.name = name; // this = new object
}
const p = new Person("Randhir"); // this inside = p

// 5. Arrow functions โ€” this is lexically inherited from enclosing scope
const timer = {
  name: "Timer",
  start() {
    setTimeout(() => {
      console.log(this.name); // "Timer" โ€” arrow inherits this from start()
    }, 1000);
  },
  startBroken() {
    setTimeout(function() {
      console.log(this.name); // undefined โ€” regular function, this = window
    }, 1000);
  }
};
timer.start(); // "Timer" โœ…

6. call(), apply(), and bind() โ€” Differences and Use Cases

function introduce(greeting, punctuation) {
  console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}

const person = { name: "Randhir" };

// call() โ€” invoke immediately, arguments passed individually
introduce.call(person, "Hello", "!"); // "Hello, I'm Randhir!"

// apply() โ€” invoke immediately, arguments passed as array
introduce.apply(person, ["Hi", "."]);  // "Hi, I'm Randhir."

// bind() โ€” returns a NEW function with this permanently bound
const boundIntroduce = introduce.bind(person, "Hey");
boundIntroduce("?"); // "Hey, I'm Randhir?"
// Can call later, this is always person

// Memory trick:
// call  = Comma separated arguments
// apply = Array of arguments
// bind  = returns Bound function (does not call immediately)
// Real use case โ€” borrowing methods
const arrayLike = { 0: "a", 1: "b", 2: "c", length: 3 };

// arrayLike is not an array, cannot use Array methods directly
const arr = Array.prototype.slice.call(arrayLike);
// ["a", "b", "c"]

// Or modern way:
const arr = Array.from(arrayLike);

// Partial application with bind
function multiply(a, b) { return a * b; }

const double = multiply.bind(null, 2); // pre-fill first argument
double(5);  // 10
double(10); // 20

const triple = multiply.bind(null, 3);
triple(5);  // 15

7. Implement Debounce and Throttle From Scratch

Always asked at senior level โ€” you must implement both, not just explain them. Knowing the difference is not enough.

// Debounce โ€” delay execution until user stops triggering
// Use case: search input, window resize handler, form validation
// "Wait until the user stops typing for 300ms, then fire"

function debounce(fn, delay) {
  let timer = null;

  return function(...args) {
    clearTimeout(timer); // cancel previous scheduled call

    timer = setTimeout(() => {
      fn.apply(this, args); // call with correct context and args
    }, delay);
  };
}

// Usage
const handleSearch = debounce((query) => {
  console.log("Searching for:", query);
  // API call here
}, 300);

searchInput.addEventListener("input", (e) => handleSearch(e.target.value));
// Only fires 300ms after user stops typing
// Throttle โ€” limit execution to at most once per interval
// Use case: scroll events, mouse move, button click spam prevention
// "Fire at most once every 200ms no matter how many events"

function throttle(fn, limit) {
  let lastCall = 0;

  return function(...args) {
    const now = Date.now();

    if (now - lastCall >= limit) {
      lastCall = now;
      fn.apply(this, args);
    }
  };
}

// Usage
const handleScroll = throttle(() => {
  console.log("Scroll position:", window.scrollY);
  // Update UI, load more content, etc.
}, 200);

window.addEventListener("scroll", handleScroll);
// Fires max once every 200ms even if scroll event fires 100 times/second
// Key difference:
// Debounce โ†’ fires ONCE after silence period ends
//   Good for: search, autocomplete, resize
// Throttle โ†’ fires REGULARLY at fixed interval
//   Good for: scroll, mouse move, rate limiting

// Debounce with immediate option (fire on leading edge)
function debounce(fn, delay, immediate = false) {
  let timer = null;

  return function(...args) {
    const callNow = immediate && !timer;
    clearTimeout(timer);

    timer = setTimeout(() => {
      timer = null;
      if (!immediate) fn.apply(this, args);
    }, delay);

    if (callNow) fn.apply(this, args);
  };
}

8. How Do Promises Work Under the Hood?

// Promise states: pending โ†’ fulfilled OR rejected (irreversible)

// A Promise is an object that represents the eventual result of an async operation
const promise = new Promise((resolve, reject) => {
  // executor function runs synchronously
  console.log("executor runs"); // synchronous

  setTimeout(() => {
    resolve("success"); // transitions pending โ†’ fulfilled
    // or: reject(new Error("failed")) // transitions pending โ†’ rejected
  }, 1000);
});

// .then() callbacks are microtasks โ€” run after current synchronous code
promise.then(value => console.log(value));
console.log("after then"); // runs BEFORE the then callback

// Output:
// "executor runs"
// "after then"
// "success" (after 1 second)
// Promise chaining โ€” each .then() returns a new Promise
fetch('/api/user')
  .then(res => res.json())              // returns Promise with parsed JSON
  .then(user => fetch(`/api/posts/${user.id}`)) // returns new fetch Promise
  .then(res => res.json())
  .then(posts => console.log(posts))
  .catch(err => console.error(err));    // catches ANY error in the chain

// async/await is syntactic sugar over Promises
// Under the hood it still uses the microtask queue

async function getUser() {
  try {
    const res  = await fetch('/api/user');   // suspends here, frees event loop
    const user = await res.json();           // suspends again
    return user;                             // wraps in resolved Promise
  } catch (err) {
    throw err; // wraps in rejected Promise
  }
}

// Promise.all vs Promise.allSettled vs Promise.race vs Promise.any
const [users, posts] = await Promise.all([getUsers(), getPosts()]); // fail-fast
const results = await Promise.allSettled([api1(), api2()]); // never rejects
const fastest = await Promise.race([primary(), fallback()]); // first to settle
const first   = await Promise.any([api1(), api2()]); // first to fulfill

9. WeakMap and WeakSet โ€” When and Why?

// Regular Map holds strong references โ€” prevents garbage collection
const map = new Map();
let obj = { name: "Randhir" };
map.set(obj, "some data");
obj = null; // obj still exists in memory โ€” map holds a reference!

// WeakMap holds WEAK references โ€” allows garbage collection
const weakMap = new WeakMap();
let obj = { name: "Randhir" };
weakMap.set(obj, "some data");
obj = null; // now obj CAN be garbage collected โ€” weakMap won't prevent it

// WeakMap limitations:
// Keys must be objects (not primitives)
// Not iterable โ€” no forEach, no size, no keys()
// Cannot be cleared

// Real use case 1 โ€” private data for class instances
const _private = new WeakMap();

class BankAccount {
  constructor(balance) {
    _private.set(this, { balance }); // truly private
  }

  getBalance() {
    return _private.get(this).balance;
  }

  deposit(amount) {
    _private.get(this).balance += amount;
  }
}

const account = new BankAccount(1000);
account.getBalance(); // 1000
// account.balance โ†’ undefined (no direct access)
// _private โ†’ internal, not accessible outside

// Real use case 2 โ€” caching without memory leaks
const cache = new WeakMap();

function processUser(user) {
  if (cache.has(user)) return cache.get(user);

  const result = expensiveComputation(user);
  cache.set(user, result);
  return result;
}
// When user object is garbage collected, cache entry is automatically removed
// Regular Map would cause memory leak โ€” entries never removed

10. How Does the V8 Engine and JIT Compilation Work?

// V8 is Google's JavaScript engine โ€” used in Chrome and Node.js
// JavaScript execution pipeline in V8:

// 1. Parsing
//    Source code โ†’ AST (Abstract Syntax Tree)
//    Checks for syntax errors

// 2. Ignition (Interpreter)
//    AST โ†’ bytecode
//    Executes bytecode line by line
//    Fast startup, slower execution

// 3. TurboFan (JIT Compiler)
//    Profiles running code
//    Identifies "hot" functions (called frequently)
//    Compiles hot bytecode to optimized native machine code
//    Deoptimizes if assumptions are wrong (type changes)

// Why type consistency matters for performance:
function add(a, b) { return a + b; }

add(1, 2);     // V8 optimizes assuming numbers
add(3, 4);     // fast โ€” still numbers
add("5", "6"); // DEOPTIMIZATION โ€” type changed to string
               // V8 must throw away optimized code and recompile

// Hidden classes โ€” V8 optimization for object property access
function Point(x, y) {
  this.x = x;
  this.y = y;
}

// โœ… Same hidden class โ€” V8 optimizes property access
const p1 = new Point(1, 2);
const p2 = new Point(3, 4);

// โŒ Different hidden classes โ€” defeats optimization
const p1 = { x: 1, y: 2 };
const p2 = { y: 2, x: 1 }; // different property order = different hidden class

11. Common JavaScript Memory Leaks and How to Detect Them

// Memory leak 1 โ€” accidental global variables
function leak() {
  leakedVar = "I am global"; // no var/let/const โ€” becomes window property
}
// Fix: always use let/const, use 'use strict'

// Memory leak 2 โ€” forgotten timers
const timer = setInterval(() => {
  // references to DOM elements or large objects here
  process(data);
}, 1000);
// If you never clearInterval() โ€” timer runs forever, keeping references alive
// Fix: always store timer reference and clear it when done
clearInterval(timer);

// Memory leak 3 โ€” closures holding large data
function outer() {
  const largeData = new Array(1000000).fill("x");

  return function inner() {
    console.log("inner called");
    // inner does not use largeData but closure keeps it alive
  };
}
const fn = outer(); // largeData stays in memory as long as fn exists

// Memory leak 4 โ€” removed DOM nodes still referenced
let element = document.getElementById("myDiv");
document.body.removeChild(element);
// element removed from DOM but still in memory โ€” JS variable holds reference
element = null; // Fix: explicitly null the reference

// Memory leak 5 โ€” event listeners not removed
const button = document.getElementById("btn");
function handleClick() { /* ... */ }
button.addEventListener("click", handleClick);
// If button is removed from DOM but listener is not removed โ€” leak
// Fix: always remove listeners when component is destroyed
button.removeEventListener("click", handleClick);

// How to detect memory leaks:
// Chrome DevTools โ†’ Memory tab
// Take heap snapshot before and after an action
// Compare snapshots โ€” growing objects indicate leaks
// Use Performance Monitor to watch JS heap size over time

12. Generators and Iterators โ€” Practical Use Cases

// Iterator protocol โ€” object with next() method returning {value, done}
const range = {
  from: 1,
  to: 5,
  [Symbol.iterator]() {
    let current = this.from;
    const last  = this.to;
    return {
      next() {
        return current <= last
          ? { value: current++, done: false }
          : { value: undefined, done: true };
      }
    };
  }
};

for (let num of range) {
  console.log(num); // 1, 2, 3, 4, 5
}

// Generator function โ€” cleaner way to create iterators
function* rangeGenerator(from, to) {
  for (let i = from; i <= to; i++) {
    yield i; // pause here, return i, resume on next next() call
  }
}

for (let num of rangeGenerator(1, 5)) {
  console.log(num); // 1, 2, 3, 4, 5
}

// Real use case 1 โ€” infinite sequence without memory issues
function* fibonacci() {
  let [a, b] = [0, 1];
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

const fib = fibonacci();
fib.next().value; // 0
fib.next().value; // 1
fib.next().value; // 1
fib.next().value; // 2
fib.next().value; // 3
// Never loads all values into memory

// Real use case 2 โ€” async flow control (basis of async/await)
function* fetchUser() {
  const user   = yield fetch('/api/user');
  const posts  = yield fetch(`/api/posts/${user.id}`);
  return posts;
}
// Libraries like co.js use this pattern under the hood

13. Proxy and Reflect API โ€” What Are They and When to Use Them?

// Proxy โ€” intercept and customize operations on objects
// new Proxy(target, handler)

const user = { name: "Randhir", age: 25 };

const userProxy = new Proxy(user, {
  // Intercept property reads
  get(target, property) {
    console.log(`Getting ${property}`);
    return property in target ? target[property] : `Property ${property} not found`;
  },

  // Intercept property writes
  set(target, property, value) {
    if (property === "age" && typeof value !== "number") {
      throw new TypeError("Age must be a number");
    }
    if (property === "age" && value < 0) {
      throw new RangeError("Age cannot be negative");
    }
    target[property] = value;
    return true; // must return true to indicate success
  },

  // Intercept delete operations
  deleteProperty(target, property) {
    if (property === "name") throw new Error("Cannot delete name");
    delete target[property];
    return true;
  }
});

userProxy.name;       // "Randhir" + logs "Getting name"
userProxy.age = -1;   // RangeError: Age cannot be negative
userProxy.missing;    // "Property missing not found"

// Real use case โ€” reactive data (how Vue 3 reactivity works)
function reactive(obj) {
  return new Proxy(obj, {
    set(target, key, value) {
      target[key] = value;
      console.log(`${key} changed to ${value} โ€” update DOM`);
      return true;
    }
  });
}

const state = reactive({ count: 0 });
state.count = 1; // "count changed to 1 โ€” update DOM"

// Reflect โ€” companion to Proxy, provides default behaviour
const handler = {
  get(target, property, receiver) {
    console.log(`Getting ${property}`);
    return Reflect.get(target, property, receiver); // default get behaviour
  }
};

14. CommonJS vs ES Modules โ€” Differences and Interoperability

// CommonJS โ€” Node.js original module system
// Synchronous loading โ€” fine for server, bad for browser
// require() / module.exports

// math.js (CommonJS)
function add(a, b) { return a + b; }
module.exports = { add };

// app.js
const { add } = require('./math');
add(2, 3); // 5

// ES Modules (ESM) โ€” official JavaScript standard (ES6)
// Asynchronous loading โ€” works in both browser and Node.js
// import / export

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

// app.js
import Calculator, { add, PI } from './math.js';
import * as MathUtils from './math.js'; // import all named exports
CommonJS ES Modules
Loading Synchronous Asynchronous
Syntax require / module.exports import / export
Tree shaking โŒ No โœ… Yes
Top-level await โŒ No โœ… Yes
Dynamic import require() anywhere import() returns Promise
Default in Node.js (.js files) Browsers, .mjs, type:module

15. Web Workers โ€” True Parallelism in JavaScript

// JavaScript is single-threaded โ€” the event loop handles concurrency
// Web Workers run in a separate thread โ€” true parallelism
// Workers cannot access: DOM, window, document

// main.js
const worker = new Worker('worker.js');

// Send data to worker
worker.postMessage({ numbers: [1, 2, 3, 4, 5], operation: 'sum' });

// Receive result from worker
worker.onmessage = function(event) {
  console.log('Result from worker:', event.data.result);
};

worker.onerror = function(error) {
  console.error('Worker error:', error);
};

// Terminate worker when done
// worker.terminate();

// worker.js (runs in separate thread)
self.onmessage = function(event) {
  const { numbers, operation } = event.data;

  let result;
  if (operation === 'sum') {
    result = numbers.reduce((acc, n) => acc + n, 0);
  }

  // Send result back to main thread
  self.postMessage({ result });
};

// Real use case โ€” heavy computation without freezing UI
// โœ… Good for: image processing, encryption, large data parsing, sorting
// โŒ Not for: DOM manipulation (not accessible), simple tasks (overhead not worth it)

// Shared Workers โ€” shared between multiple browser tabs
const shared = new SharedWorker('shared-worker.js');
shared.port.start();
shared.port.postMessage('hello');
shared.port.onmessage = (e) => console.log(e.data);

Common Mistakes Senior Developers Make in Interviews

  • Explaining what something is without explaining why it works that way โ€” interviewers want the mechanism, not the definition.
  • Not knowing the difference between microtasks and macrotasks โ€” most candidates know setTimeout is async but cannot explain why Promise.then always runs before it.
  • Implementing debounce without handling the this context โ€” the fn.apply(this, args) part is what trips most candidates.
  • Saying "JavaScript is multi-threaded because of async/await" โ€” it is single-threaded. Async/await is cooperative concurrency via the event loop, not parallelism.
  • Not having a real production example for closures or WeakMap โ€” attach a specific situation from your own experience to every answer.

Related Interview Guides

Subscribe to Our Newsletter

Join Our Developer Community!

Unsubscribe Anytime | No Spam