JavaScript16 min read

JavaScript Interview Questions: 25 Questions You Should Know

Master the 25 essential JavaScript interview questions covering primitives, closures, event loop, promises, prototypes, and memory leaks with runnable code examples.

Pairlet TeamPublished: 2026-09-08

JavaScript interviews assess both foundational runtime knowledge and practical engineering problem-solving. Whether you are preparing for a senior frontend role or designing interview loops for your team, these 25 core questions cover the essential concepts every JavaScript engineer should master.

1. Primitives vs. Reference Types

JavaScript has 7 primitive types: string, number, bigint, boolean, undefined, symbol, and null. All primitives are immutable and passed by value.

Reference types (Object, Array, Function, Map, Set) are mutable and passed by reference:

javascriptPairlet Snippet
let a = 10;
let b = a;
b = 20;

let obj1 = { name: "Alice" }; let obj2 = obj1; obj2.name = "Bob"; console.log(obj1.name); // "Bob" (reference types share memory location) ```

2. var vs let vs const

  • var: Function-scoped, hoisted with undefined initialization, allows redeclaration.
  • let: Block-scoped, hoisted but resides in the Temporal Dead Zone (TDZ) until evaluation, prevents redeclaration.
  • const: Block-scoped, resides in TDZ, requires immediate initialization, prevents variable reassignment (though internal object properties remain mutable).
javascriptPairlet Snippet
function scopeExample() {
  if (true) {
    var x = 1;
    let y = 2;
    const z = 3;
  }
  console.log(x); // 1
  // console.log(y); // ReferenceError: y is not defined
}

3. Lexical Scope and Closures

A closure is the combination of a function bundled together with references to its surrounding state (lexical environment). Closures give inner functions access to outer function scope variables even after the outer function has returned.

javascriptPairlet Snippet
function createCounter(initialValue = 0) {
  let count = initialValue;
  return {
    increment() { count++; return count; },
    decrement() { count--; return count; },
    getValue() { return count; }
  };

const counter = createCounter(5); console.log(counter.increment()); // 6 console.log(counter.getValue()); // 6 ```

4. Variable Hoisting & Temporal Dead Zone (TDZ)

Hoisting is JavaScript's default behavior of moving declarations to the top of the current scope during execution context creation.

javascriptPairlet Snippet
console.log(a); // undefined (var hoisted)

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

5. Execution Context and the this Binding

The value of this is determined dynamically at call time (except for arrow functions, which inherit this lexically from their enclosing context):

1. Implicit Binding: obj.method()this is obj. 2. Explicit Binding: fn.call(ctx), fn.apply(ctx), fn.bind(ctx). 3. New Binding: new Constructor()this is the newly created instance. 4. Default Binding: Plain function invocation → window (browser non-strict), undefined (strict mode).

6. Prototypes and Prototypal Inheritance

Every JavaScript object has an internal [[Prototype]] link to another object. Property lookups traverse up the prototype chain until the property is found or null is reached.

javascriptPairlet Snippet
const animal = { eats: true };
const dog = Object.create(animal);

console.log(dog.bark); // true (own property) console.log(dog.eats); // true (inherited from animal prototype) ```

7. Promises and Microtasks

A Promise represents the eventual completion or failure of an asynchronous operation. Promise resolution callbacks (.then(), .catch(), .finally()) are scheduled as microtasks.

8. async / await Syntax

Syntactic sugar built on top of Promises and Generators. An async function always returns a Promise. await pauses execution inside the function until the awaited Promise resolves or rejects.

javascriptPairlet Snippet
async function fetchUserData(userId) {
  try {
    const res = await fetch(`/api/users/${userId}`);
    if (!res.ok) throw new Error("User not found");
    return await res.json();
  } catch (err) {
    console.error("Failed to fetch user:", err);
    throw err;
  }
}

9. Event Loop: Microtasks vs Macrotasks

The Event Loop executes tasks in the following order: 1. Execute synchronous script in call stack. 2. Drain the Microtask Queue completely (Promise callbacks, queueMicrotask, MutationObserver). 3. Pick one task from the Macrotask Queue (setTimeout, setInterval, requestAnimationFrame, I/O). 4. Render UI updates if needed. 5. Repeat.

10. Map vs Filter vs Reduce

  • map: Transforms every element, returning a new array of equal length.
  • filter: Returns a new array containing elements that satisfy a predicate condition.
  • reduce: Accumulates elements into a single value (number, object, or array).
javascriptPairlet Snippet
const numbers = [1, 2, 3, 4, 5];
const doubledEvensSum = numbers
  .filter(n => n % 2 === 0)
  .map(n => n * 2)
  .reduce((acc, curr) => acc + curr, 0); // (2*2) + (4*2) = 12

11. Debounce Implementation

Debouncing ensures a function is not called until a specified delay has elapsed since its last invocation.

javascriptPairlet Snippet
function debounce(fn, delayMs) {
  let timerId = null;
  return function (...args) {
    if (timerId) clearTimeout(timerId);
    timerId = setTimeout(() => {
      fn.apply(this, args);
      timerId = null;
    }, delayMs);
  };
}
```

12. Throttle Implementation

Throttling guarantees a function executes at most once per specified time interval.

javascriptPairlet Snippet
function throttle(fn, intervalMs) {
  let lastExecTime = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastExecTime >= intervalMs) {
      lastExecTime = now;
      fn.apply(this, args);
    }
  };
}

13. Shallow Copy vs. Deep Copy

  • Shallow Copy: Copies top-level properties. Nested objects retain shared references (Object.assign(), spread operator {...obj}).
  • Deep Copy: Recursively duplicates all nested objects and arrays (structuredClone(), custom recursive copy function).
javascriptPairlet Snippet
const original = { a: 1, b: { c: 2 } };
const shallow = { ...original };

const deep = structuredClone(original); deep.b.c = 42; // Independent copy ``` *Practice implementing custom Deep Clone in an interview session.*

14. Strict Equality (===) vs Loose Equality (==)

=== checks equality without type coercion. == performs implicit type conversion before comparison, leading to subtle bugs:

javascriptPairlet Snippet
0 == '0';   // true
0 === '0';  // false
null == undefined; // true
null === undefined; // false

15. Destructuring & Rest / Spread Operators

Destructuring unpacks values from arrays or properties from objects into distinct variables. Rest (...) collects remaining properties, while spread expands iterables into elements.

javascriptPairlet Snippet
const { name, ...details } = { name: "Pairlet", type: "Live Coding", price: 0 };
console.log(details); // { type: "Live Coding", price: 0 }

16. ES Modules vs CommonJS

  • ES Modules (import / export): Static structure, evaluated at compile/parse time, supports tree-shaking, strict mode by default.
  • CommonJS (require() / module.exports): Dynamic loading, evaluated synchronously at runtime (Node.js legacy default).

17. Error Handling Best Practices

Always wrap asynchronous operations in try/catch blocks or attach .catch() handlers. Re-throw unhandled domain errors or wrap them in custom Error subclasses.

18. JavaScript Memory Leaks

Common causes of memory leaks in JavaScript applications: 1. Unexpected global variables. 2. Forgotten timers (setInterval) or unremoved event listeners. 3. Out-of-DOM references holding detached DOM nodes. 4. Unbound closures keeping large scopes alive.

19. Garbage Collection Mechanics

JavaScript engines use a Mark-and-Sweep algorithm. Starting from root references (globalThis, stack variables), the collector marks all reachable objects. Any unmarked objects are deemed unreachable and garbage collected.

20. Event Delegation

Event delegation leverages event bubbling to attach a single event listener to a parent element rather than attaching individual listeners to multiple child nodes.

javascriptPairlet Snippet
document.getElementById("list").addEventListener("click", (e) => {
  if (e.target && e.target.nodeName === "LI") {
    console.log("Clicked item:", e.target.textContent);
  }
});

21. Currying

Currying transforms a function with multiple arguments into a sequence of nested functions that each accept a single argument.

javascriptPairlet Snippet
const curry = (fn) =>
  function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    return (...nextArgs) => curried.apply(this, args.concat(nextArgs));

const add = (a, b, c) => a + b + c; const curriedAdd = curry(add); console.log(curriedAdd(1)(2)(3)); // 6 ```

22. Generator Functions & Iterators

Generators (function*) allow pausing and resuming function execution using the yield keyword, yielding sequence iterators on demand.

23. Concurrency in Single-Threaded JS

JavaScript achieves non-blocking concurrency despite being single-threaded by delegating asynchronous I/O (network requests, timers, file system ops) to the underlying system via Libuv (in Node) or Web APIs (in browsers).

24. Object.freeze() vs Object.seal()

  • Object.freeze(): Prevents adding, deleting, or modifying existing property values (shallow immutability).
  • Object.seal(): Prevents adding or deleting properties, but allows modifying existing property values.

25. Symbol & WeakMap / WeakSet

  • Symbol: Guaranteed unique primitive identifier, ideal for non-colliding object property keys.
  • WeakMap / WeakSet: Holds weak references to key objects, allowing key objects to be garbage-collected if no other references exist.

---

Practice JavaScript Interviews Live Are you evaluating JavaScript candidates or preparing for an upcoming technical loop? You can practice coding problems live in a zero-setup collaborative editor. [Create a free Pairlet interview room](https://www.pairlet.dev/interview/new) or explore our [coding problem library](https://www.pairlet.dev/problems).

Frequently Asked Questions

What is the difference between primitives and reference types in JavaScript?

Primitive values (number, string, boolean, null, undefined, symbol, bigint) are immutable and stored directly by value. Objects, arrays, and functions are reference types, stored in memory as references; mutating a property alters all references pointing to that object.

How does the JavaScript event loop handle microtasks vs macrotasks?

Microtasks (Promises, queueMicrotask, MutationObserver) are processed continuously until the microtask queue is empty after every task execution. Macrotasks (setTimeout, setInterval, setImmediate, I/O) are picked one at a time from the task queue after the microtask queue has drained completely.

Practice Relevant Coding Problems
Practice Live Coding

Conduct Live Coding Interviews with Zero Friction

No candidate sign-up required. Create an instant room, share the link, and code together in real time with shared code execution.

Related Articles