JS

Top 50 JavaScript Interview Questions with Code Examples

A curated set of 50 must-know JavaScript interview questions with clear answers and concept-clarifying code examples. Covers fundamentals and type coercion, scope and closures, functions and this, objects and prototypes, the event loop and async/await, and modern ES6+ features — for frontend, full-stack and SDE interviews.

50questions
7categories
10beginner
22intermediate
18advanced
Showing 50 of 50 questions

🔤 Fundamentals & Types (8)

Q1.What are the data types in JavaScript?Beginner

JavaScript has 7 primitive types — string, number, boolean, null, undefined, symbol, bigint — and one non-primitive type, object (which includes arrays, functions, dates, etc.). Primitives are immutable and compared by value; objects are mutable and compared by reference. typeof reveals the type, with one historical quirk: typeof null returns 'object'.

💻 Code example
typeof "hi";        // "string"
typeof 42;          // "number"
typeof true;        // "boolean"
typeof undefined;   // "undefined"
typeof null;        // "object"   ← historical bug
typeof 10n;         // "bigint"
typeof Symbol();    // "symbol"
typeof {};          // "object"
typeof [];          // "object"   (use Array.isArray([]))
typeof function(){};// "function"
Q2.What's the difference between null and undefined?Beginner

undefined means a variable has been declared but not assigned a value (or a function returned nothing, or a missing object property). null is an intentional assignment representing 'no value'. typeof undefined is 'undefined'; typeof null is 'object'. They are loosely equal (==) but not strictly equal (===).

💻 Code example
let a;
console.log(a);            // undefined (declared, not assigned)

let b = null;
console.log(b);            // null (explicit 'no value')

console.log(null == undefined);   // true  (loose)
console.log(null === undefined);  // false (strict)

function f() {}
console.log(f());          // undefined (no return)
Q3.What is the difference between == and === in JavaScript?Beginner

== (loose equality) compares values after type coercion — it converts operands to a common type first. === (strict equality) compares both value and type with no coercion. Always prefer === to avoid surprising bugs from implicit conversion.

💻 Code example
5 == "5";     // true  (string coerced to number)
5 === "5";    // false (different types)

0 == false;   // true  (false → 0)
0 === false;  // false

null == undefined;  // true
NaN == NaN;         // false (NaN never equals anything)
Q4.What is type coercion in JavaScript?Intermediate

Type coercion is JavaScript automatically converting a value from one type to another. It happens implicitly in operations mixing types (like + with a string, or == comparisons) and explicitly via Number(), String(), Boolean(). The + operator concatenates if either operand is a string; other arithmetic operators convert to numbers.

💻 Code example
"5" + 3;      // "53"  (number → string, concatenation)
"5" - 3;      // 2     (string → number)
"5" * "2";    // 10
true + 1;     // 2     (true → 1)
[] + {};      // "[object Object]"
Number("");   // 0
Boolean("");  // false
!!"hello";    // true  (truthy)
Q5.What are truthy and falsy values in JavaScript?Intermediate

Every value is either truthy or falsy in a boolean context (if, &&, ||, !). There are exactly 8 falsy values: false, 0, -0, 0n, '' (empty string), null, undefined, and NaN. Everything else is truthy — including '0', 'false', [], {}, and functions.

💻 Code example
// All falsy:
if (!false && !0 && !"" && !null && !undefined && !NaN && !0n) {
  console.log("all falsy");   // prints
}

// Surprisingly truthy:
if ([] && {} && "0" && "false") {
  console.log("all truthy");  // prints
}
Q6.Why does NaN === NaN return false, and how do you check for NaN?Intermediate

NaN (Not-a-Number) is the only JavaScript value not equal to itself, per the IEEE-754 spec. So NaN === NaN is false. To reliably detect NaN, use Number.isNaN() (strict — only true for actual NaN) rather than the global isNaN() (which coerces its argument first and gives false positives).

💻 Code example
NaN === NaN;            // false

Number.isNaN(NaN);      // true
Number.isNaN("abc");    // false (no coercion)

isNaN("abc");           // true  (coerces "abc" → NaN) — misleading
isNaN("123");           // false

Object.is(NaN, NaN);    // true  (also works)
Q7.What is the difference between primitive and reference types?Intermediate

Primitives (string, number, boolean, null, undefined, symbol, bigint) are stored by value — assigning or passing one copies the value. Reference types (objects, arrays, functions) are stored by reference — variables hold a pointer to the same object, so mutations through one variable are visible through another.

💻 Code example
// Primitive — copied by value
let a = 1;
let b = a;
b = 99;
console.log(a);   // 1 (unaffected)

// Reference — copied by reference
let x = { n: 1 };
let y = x;
y.n = 99;
console.log(x.n); // 99 (same object)
Q8.How do you make a deep copy vs a shallow copy of an object?Advanced

A shallow copy duplicates only the top level — nested objects are still shared by reference (spread, Object.assign). A deep copy duplicates everything recursively, so nested objects are independent. Modern deep copy: structuredClone(); older approach: JSON.parse(JSON.stringify()) (but it drops functions, undefined, Dates become strings, and fails on circular references).

💻 Code example
const orig = { a: 1, nested: { b: 2 } };

// Shallow — nested is shared
const shallow = { ...orig };
shallow.nested.b = 99;
console.log(orig.nested.b);  // 99 (leaked!)

// Deep — fully independent
const deep = structuredClone(orig);
deep.nested.b = 7;
console.log(orig.nested.b);  // unchanged

🔒 Scope, Hoisting & Closures (6)

Q9.What is the difference between var, let and const?Beginner

var is function-scoped and hoisted as undefined. let and const are block-scoped and hoisted into the Temporal Dead Zone (accessing them before declaration throws). const cannot be reassigned, but if it holds an object/array the contents can still mutate. Default to const, use let when reassigning, avoid var.

💻 Code example
function demo() {
  if (true) {
    var a = 1;   // function-scoped
    let b = 2;   // block-scoped
    const c = 3; // block-scoped, no reassign
  }
  console.log(a);  // 1
  console.log(b);  // ReferenceError: b is not defined
}

const obj = { x: 1 };
obj.x = 2;        // OK (mutating contents)
// obj = {};      // TypeError (reassigning binding)
Q10.What is hoisting in JavaScript?Intermediate

Hoisting is JavaScript moving declarations to the top of their scope at compile time. var declarations are hoisted and initialized to undefined. Function declarations are fully hoisted (callable before they appear). let/const are hoisted but NOT initialized — they sit in the Temporal Dead Zone until the declaration line runs. Function expressions and arrow functions are not hoisted as functions.

💻 Code example
console.log(x);   // undefined (var hoisted, not yet assigned)
var x = 5;

sayHi();          // "hi" (function declaration fully hoisted)
function sayHi() { console.log("hi"); }

console.log(y);   // ReferenceError (TDZ)
let y = 10;

foo();            // TypeError: foo is not a function
var foo = () => {};
Q11.What is a closure in JavaScript?Advanced

A closure is a function bundled together with references to its surrounding lexical scope. Even after the outer function returns, the inner function retains access to the outer variables. Closures enable private state, function factories, currying, and memoization. They capture variables by reference, not by value.

💻 Code example
function makeCounter() {
  let count = 0;                 // private to this closure
  return {
    inc: () => ++count,
    get: () => count,
  };
}

const c = makeCounter();
c.inc();
c.inc();
console.log(c.get());  // 2
console.log(c.count);  // undefined (truly private)
Q12.Why does a var loop print the same value, and how does let fix it?Advanced

var is function-scoped, so all loop iterations share ONE binding of i. By the time the deferred callbacks run, the loop has finished and i has its final value. let creates a NEW binding per iteration, so each callback captures its own i. This is the classic closure-in-loop interview question.

💻 Code example
// Bug: var — all log 3
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// Output: 3, 3, 3

// Fix: let — new binding per iteration
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// Output: 0, 1, 2
Q13.What is the Temporal Dead Zone (TDZ)?Intermediate

The Temporal Dead Zone is the period between entering a scope and the actual declaration of a let/const variable. During the TDZ the variable exists but is uninitialized, so accessing it throws a ReferenceError. This is different from var, which would return undefined. The TDZ helps catch use-before-declaration bugs.

💻 Code example
{
  // TDZ for `name` starts here
  console.log(name); // ReferenceError: Cannot access 'name'
                     // before initialization
  let name = "Sam";  // TDZ ends here
  console.log(name); // "Sam"
}

// Compare with var (no TDZ):
console.log(v);      // undefined
var v = 1;
Q14.What is an IIFE (Immediately Invoked Function Expression)?Advanced

An IIFE is a function that is defined and executed immediately. It creates a private scope so variables don't leak to the global scope — historically the main way to avoid global pollution before block scoping (let/const) and ES modules existed. It's written by wrapping a function in parentheses and calling it.

💻 Code example
(function () {
  const secret = 42;        // not global
  console.log(secret);      // 42
})();

// Arrow IIFE
(() => {
  console.log("runs once");
})();

// console.log(secret); // ReferenceError — scope is private

🎯 Functions & this (6)

Q15.How does the 'this' keyword work in JavaScript?Advanced

this refers to the execution context and is determined by HOW a function is called, not where it's defined. Rules: a method call (obj.fn()) → obj; a plain call (fn()) → undefined in strict mode (or window otherwise); new Fn() → the new instance; call/apply/bind → an explicit value; arrow functions → inherit this from the enclosing lexical scope.

💻 Code example
const obj = {
  name: "Sam",
  regular() { return this.name; },
  arrow: () => this?.name,
};

obj.regular();          // "Sam" (this = obj)
const fn = obj.regular;
fn();                   // undefined (plain call, this lost)

obj.arrow();            // undefined (arrow inherits outer this)
Q16.What is the difference between call, apply and bind?Advanced

All three set the this value of a function. call invokes the function immediately with arguments listed individually. apply invokes immediately with arguments as an array. bind does NOT invoke — it returns a new function with this (and optionally some arguments) permanently bound, to call later.

💻 Code example
function greet(greeting, punct) {
  return greeting + ", " + this.name + punct;
}
const person = { name: "Sam" };

greet.call(person, "Hi", "!");        // "Hi, Sam!"
greet.apply(person, ["Hello", "."]);  // "Hello, Sam."

const bound = greet.bind(person, "Hey");
bound("?");                            // "Hey, Sam?" (called later)
Q17.What is the difference between an arrow function and a regular function?Intermediate

Arrow functions have no own this (they inherit it lexically), no arguments object, cannot be used as constructors (no new), and have no prototype. Regular functions get their own this based on the call site and have an arguments object. Use arrows for callbacks where you want the surrounding this; use regular functions for object methods and constructors.

💻 Code example
const obj = {
  count: 0,
  start() {
    setInterval(() => this.count++, 1000); // arrow: this = obj ✓
  },
  broken() {
    setInterval(function () {
      this.count++; // regular: this = undefined/window ✗
    }, 1000);
  },
};

// const a = () => {}; new a(); // TypeError: not a constructor
Q18.What are higher-order functions in JavaScript?Beginner

A higher-order function is one that takes a function as an argument and/or returns a function. They enable functional patterns and underpin array methods like map, filter, reduce, forEach, and sort. They make code declarative and composable.

💻 Code example
// Takes a function as an argument
const nums = [1, 2, 3, 4];
const doubled = nums.map(n => n * 2);     // [2, 4, 6, 8]
const evens = nums.filter(n => n % 2 === 0); // [2, 4]
const sum = nums.reduce((a, b) => a + b, 0); // 10

// Returns a function
const multiplier = factor => x => x * factor;
const triple = multiplier(3);
triple(5);  // 15
Q19.What is currying in JavaScript?Advanced

Currying transforms a function that takes multiple arguments into a sequence of functions each taking a single argument. It enables partial application — fixing some arguments now and supplying the rest later — and helps build reusable, composable functions.

💻 Code example
// Normal
function add(a, b, c) { return a + b + c; }
add(1, 2, 3); // 6

// Curried
const curryAdd = a => b => c => a + b + c;
curryAdd(1)(2)(3); // 6

// Partial application
const add10 = curryAdd(10);
add10(20)(30); // 60
Q20.What is the difference between function declaration and function expression?Intermediate

A function declaration is hoisted fully — you can call it before it appears in code. A function expression assigns a function to a variable; only the variable is hoisted (as undefined for var, or in the TDZ for let/const), so calling it before the assignment fails. Named function expressions help in stack traces.

💻 Code example
// Declaration — hoisted
sayHi();   // "hi" works
function sayHi() { console.log("hi"); }

// Expression — not hoisted as a function
sayBye();  // TypeError: sayBye is not a function
var sayBye = function () { console.log("bye"); };

🧬 Objects & Prototypes (6)

Q21.What is prototypal inheritance in JavaScript?Intermediate

JavaScript objects inherit from other objects via a hidden link called the prototype ([[Prototype]], accessible as __proto__). When you access a property, the engine searches the object, then its prototype, then that prototype's prototype, up the chain until found or reaching null. This is how methods like Array.prototype.map are shared by all arrays.

💻 Code example
const animal = {
  eat() { return this.name + " eats"; },
};

const dog = Object.create(animal);  // dog's prototype is animal
dog.name = "Rex";

console.log(dog.eat());                       // "Rex eats" (inherited)
console.log(Object.getPrototypeOf(dog) === animal); // true
console.log(dog.hasOwnProperty("eat"));       // false (it's on prototype)
Q22.What is the prototype chain?Advanced

The prototype chain is the series of linked objects JavaScript traverses to resolve a property or method. Every object has a [[Prototype]] pointing to another object; the chain ends at Object.prototype, whose prototype is null. Property lookups walk up this chain. Understanding it explains inheritance, method sharing, and why instanceof works.

💻 Code example
const arr = [1, 2, 3];

arr.__proto__ === Array.prototype;            // true
Array.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null;          // top of chain

// map is found on Array.prototype, not on arr
console.log(arr.hasOwnProperty("map"));       // false
console.log(arr instanceof Array);            // true
Q23.What is the difference between Object.freeze() and const?Intermediate

const prevents reassigning the variable binding but does NOT make the object immutable — you can still change its properties. Object.freeze() makes the object itself immutable (shallowly) — you can't add, remove, or change properties. For deep immutability you must freeze nested objects recursively.

💻 Code example
const obj = Object.freeze({ a: 1, nested: { b: 2 } });

obj.a = 99;          // ignored (silently in non-strict, throws in strict)
console.log(obj.a);  // 1

obj.nested.b = 99;   // works! freeze is shallow
console.log(obj.nested.b); // 99

console.log(Object.isFrozen(obj)); // true
Q24.What is destructuring in JavaScript?Intermediate

Destructuring extracts values from arrays or properties from objects into distinct variables in a single, readable statement. It supports default values, renaming, nested patterns, and the rest pattern. Widely used for function parameters, swapping variables, and pulling fields from objects.

💻 Code example
// Array destructuring
const [first, second, ...rest] = [1, 2, 3, 4];
// first=1, second=2, rest=[3,4]

// Object destructuring with rename + default
const { name: userName = "Anon", age } = { age: 30 };
// userName="Anon", age=30

// Swap without a temp
let a = 1, b = 2;
[a, b] = [b, a];   // a=2, b=1
Q25.How do you check if a property exists in an object?Advanced

Use the in operator (checks own AND inherited properties), hasOwnProperty() (own properties only), or compare to undefined (but that fails if the value IS undefined). Object.hasOwn() (ES2022) is the modern, safer replacement for hasOwnProperty.

💻 Code example
const obj = { a: 1, b: undefined };

"a" in obj;                  // true
"toString" in obj;           // true (inherited)
obj.hasOwnProperty("toString"); // false (own only)

obj.b !== undefined;         // false — misleading! b exists
"b" in obj;                  // true (correct)
Object.hasOwn(obj, "b");     // true (ES2022)
Q26.What is the difference between Map and a plain Object?Intermediate

A Map can use any value (including objects and functions) as keys; Object keys are limited to strings and symbols. Map preserves insertion order, has a .size property, is directly iterable, and performs better for frequent additions/removals. Objects are simpler for static records and JSON. Prefer Map for dynamic key-value collections.

💻 Code example
const map = new Map();
const keyObj = { id: 1 };
map.set(keyObj, "value");        // object as key — impossible with {}
map.set("name", "Sam");

console.log(map.get(keyObj));    // "value"
console.log(map.size);           // 2

for (const [k, v] of map) {      // directly iterable
  // ...
}

Async & Event Loop (7)

Q27.Is JavaScript single-threaded or multi-threaded?Beginner

JavaScript is single-threaded — it has one call stack and executes one piece of code at a time. It achieves concurrency through the event loop: long-running or async operations (timers, network, I/O) are handed to the browser/Node runtime, and their callbacks are queued and run later when the stack is empty. This avoids blocking the main thread.

💻 Code example
console.log("1");

setTimeout(() => console.log("2"), 0); // deferred to the task queue

console.log("3");

// Output: 1, 3, 2
// Even with 0ms delay, the timer callback waits for the
// synchronous code (call stack) to finish first.
Q28.Explain the event loop in JavaScript.Advanced

The event loop coordinates execution between the call stack, the microtask queue (Promise callbacks, queueMicrotask), and the macrotask queue (setTimeout, setInterval, I/O, UI events). It runs synchronous code on the stack first, then drains ALL microtasks, then takes ONE macrotask, then drains microtasks again, and repeats. Microtasks always run before the next macrotask.

💻 Code example
console.log("A");                       // 1. sync

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

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

console.log("D");                       // 2. sync

// Output: A, D, C, B
// Sync first → microtasks (C) → macrotasks (B)
Q29.What is a Promise and what are its states?Intermediate

A Promise is an object representing the eventual result of an asynchronous operation. It has three states: pending (initial), fulfilled (resolved with a value), and rejected (failed with a reason). Once settled (fulfilled or rejected) it never changes. You consume it with .then()/.catch()/.finally() or with async/await.

💻 Code example
const p = new Promise((resolve, reject) => {
  const ok = true;
  setTimeout(() => ok ? resolve("done") : reject("failed"), 100);
});

p.then(value => console.log(value))   // "done"
 .catch(err => console.error(err))
 .finally(() => console.log("settled"));
Q30.What is the difference between Promises and async/await?Intermediate

async/await is syntactic sugar over Promises that lets asynchronous code read like synchronous code. An async function always returns a Promise; await pauses the function until the awaited Promise settles, returning its value or throwing its rejection. Error handling uses try/catch instead of .catch(). Both run on the same Promise machinery.

💻 Code example
// Promise style
fetch("/api/user")
  .then(r => r.json())
  .then(user => console.log(user))
  .catch(err => console.error(err));

// async/await style — same behavior
async function load() {
  try {
    const r = await fetch("/api/user");
    const user = await r.json();
    console.log(user);
  } catch (err) {
    console.error(err);
  }
}
Q31.What is the difference between microtasks and macrotasks?Advanced

Microtasks (Promise.then callbacks, queueMicrotask, MutationObserver) have higher priority — the event loop drains the ENTIRE microtask queue after each synchronous run and after each macrotask. Macrotasks (setTimeout, setInterval, setImmediate, I/O, UI events) are processed one at a time, with microtasks flushed in between. This is why a Promise callback runs before a setTimeout(…, 0).

💻 Code example
setTimeout(() => console.log("macro 1"), 0);

Promise.resolve().then(() => {
  console.log("micro 1");
  Promise.resolve().then(() => console.log("micro 2"));
});

setTimeout(() => console.log("macro 2"), 0);

// Output: micro 1, micro 2, macro 1, macro 2
// All microtasks drain before any macrotask runs.
Q32.What's the difference between Promise.all, Promise.allSettled, Promise.race and Promise.any?Advanced

Promise.all resolves when ALL succeed (array of results) but rejects as soon as ANY rejects. Promise.allSettled waits for all to settle and never rejects — returns {status, value/reason} for each. Promise.race settles with the FIRST to settle (fulfilled or rejected). Promise.any resolves with the first to FULFILL, ignoring rejections (rejects only if all reject).

💻 Code example
const p1 = Promise.resolve(1);
const p2 = Promise.reject("err");
const p3 = Promise.resolve(3);

await Promise.all([p1, p3]);        // [1, 3]
await Promise.all([p1, p2]);        // throws "err"
await Promise.allSettled([p1, p2]); // [{status:"fulfilled",value:1},
                                    //  {status:"rejected",reason:"err"}]
await Promise.any([p2, p3]);        // 3 (first fulfilled)
await Promise.race([p2, p3]);       // throws "err" (first settled)
Q33.How do you run async operations in parallel vs sequentially?Intermediate

awaiting each operation one after another runs them sequentially (slow when they're independent). To run independent async operations in parallel, start them all first (or pass them to Promise.all) and await the combined result — total time becomes the slowest one, not the sum.

💻 Code example
// Sequential — slow (sum of both)
const a = await fetchA();   // wait 1s
const b = await fetchB();   // then wait 1s → 2s total

// Parallel — fast (max of both)
const [x, y] = await Promise.all([fetchA(), fetchB()]);
// both start immediately → ~1s total

ES6+ Features (7)

Q34.What is the spread operator and how does it differ from rest?Beginner

Both use the ... syntax but do opposite things. Spread EXPANDS an iterable into individual elements — used to copy/merge arrays and objects or pass array elements as arguments. Rest COLLECTS multiple elements into a single array — used in function parameters and destructuring to gather the remainder.

💻 Code example
// Spread — expands
const merged = [...[1, 2], ...[3, 4]];   // [1, 2, 3, 4]
const clone = { ...{ a: 1 }, b: 2 };     // { a: 1, b: 2 }
Math.max(...[5, 2, 8]);                  // 8

// Rest — collects
function sum(...nums) {                  // nums is an array
  return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3);                            // 6
Q35.What are template literals?Beginner

Template literals are strings delimited by backticks that support multi-line text and embedded expressions via ${…}, eliminating clunky concatenation. Tagged template literals let a function process the string parts and interpolated values — used for safe HTML, i18n, and styled-components.

💻 Code example
const name = "Sam";
const age = 30;

// Interpolation + multi-line
const msg = `Hello ${name},
you are ${age} years old.
Next year: ${age + 1}`;

// Tagged template
function tag(strings, ...values) {
  return strings[0] + values.map(v => `[${v}]`).join("");
}
tag`Hi ${name} and ${age}`; // "Hi [Sam][30]"
Q36.What is the difference between the ?? and || operators?Intermediate

|| (logical OR) returns the right operand if the left is ANY falsy value (0, '', false, null, undefined, NaN). ?? (nullish coalescing) returns the right operand ONLY when the left is null or undefined. Use ?? for defaults when 0 or '' are valid values you want to keep.

💻 Code example
const count = 0;

console.log(count || 10);   // 10  (0 is falsy → fallback)
console.log(count ?? 10);   // 0   (0 is not nullish → kept)

const name = "";
console.log(name || "Anon"); // "Anon"
console.log(name ?? "Anon"); // ""  (empty string kept)
Q37.What is optional chaining (?.)?Intermediate

Optional chaining (?.) short-circuits and returns undefined if a reference is null or undefined, instead of throwing a TypeError. It works on properties (a?.b), method calls (a?.b()), and array access (a?.[0]). Often combined with nullish coalescing to provide a fallback.

💻 Code example
const user = { profile: null };

console.log(user.profile.name);   // TypeError!
console.log(user.profile?.name);  // undefined (safe)
console.log(user.address?.city);  // undefined (no error)

// Method + fallback
const city = user.getCity?.() ?? "Unknown";
console.log(city);                // "Unknown"
Q38.What is the difference between a Set and an array?Intermediate

A Set is a collection of UNIQUE values — it automatically removes duplicates and offers O(1) has() lookups. An array is an ordered list that allows duplicates and is indexed. Use a Set to deduplicate or test membership efficiently; convert between them with the spread operator or Array.from.

💻 Code example
const arr = [1, 2, 2, 3, 3, 3];

// Deduplicate
const unique = [...new Set(arr)];   // [1, 2, 3]

const s = new Set(arr);
s.has(2);     // true  (O(1))
s.size;       // 3
s.add(4);
s.delete(1);
[...s];       // [2, 3, 4]
Q39.What is the difference between forEach and map?Beginner

Both iterate over an array, but map RETURNS a new array with the transformed results, while forEach returns undefined and is used purely for side effects. map is chainable and ideal for transformations; forEach is for doing something with each element (logging, mutating external state). Neither map nor forEach can be break-ed out of early.

💻 Code example
const nums = [1, 2, 3];

// map — returns a new array
const doubled = nums.map(n => n * 2);  // [2, 4, 6]

// forEach — returns undefined
const result = nums.forEach(n => n * 2);
console.log(result);                   // undefined

nums.map(n => n * 2).filter(n => n > 2); // chainable
Q40.What are ES modules (import/export)?Advanced

ES modules are the standard way to split JavaScript into reusable files with their own scope. Use export (named or default) to expose values and import to consume them. Modules are loaded once, are strict-mode by default, support static analysis (tree-shaking), and have live bindings. They replaced older CommonJS (require/module.exports) and IIFE patterns.

💻 Code example
// math.js
export const PI = 3.14159;
export function area(r) { return PI * r * r; }
export default function square(x) { return x * x; }

// app.js
import square, { PI, area } from "./math.js";
import * as math from "./math.js";

square(4);     // 16
area(2);       // 12.56636
math.PI;       // 3.14159

🚀 Advanced & Patterns (10)

Q41.What is debouncing, and how do you implement it?Advanced

Debouncing delays running a function until a pause in activity — it waits until the events stop firing for a set time, then runs once. Ideal for search-as-you-type, resize, and autosave, where you only care about the final state. Each new call resets the timer.

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

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

// Rapid calls — only the last one fires after 300ms of silence
onSearch("a"); onSearch("ap"); onSearch("app");
Q42.What is throttling, and how is it different from debouncing?Advanced

Throttling limits a function to run at most once per time interval, no matter how often it's called — good for scroll, mousemove, and rate-limiting. Debouncing waits for activity to STOP before running once. Rule of thumb: throttle for steady periodic updates during continuous events; debounce for acting only after the user finishes.

💻 Code example
function throttle(fn, limit) {
  let inThrottle = false;
  return function (...args) {
    if (!inThrottle) {
      fn.apply(this, args);
      inThrottle = true;
      setTimeout(() => (inThrottle = false), limit);
    }
  };
}

const onScroll = throttle(() => console.log("scroll"), 200);
window.addEventListener("scroll", onScroll); // fires at most every 200ms
Q43.What is event delegation?Advanced

Event delegation attaches a single event listener to a parent element and uses event bubbling plus event.target to handle events from many (current and future) child elements. It's more memory-efficient than attaching a listener to each child and automatically works for dynamically added elements.

💻 Code example
// Instead of a listener per <li>, one on the <ul>
document.querySelector("#list").addEventListener("click", e => {
  if (e.target.matches("li.item")) {
    console.log("clicked:", e.target.textContent);
  }
});

// Works even for <li>s added later — no rebinding needed
// document.querySelector("#list").innerHTML += "<li class='item'>New</li>";
Q44.What is memoization, and how do you implement it?Advanced

Memoization is an optimization that caches a function's results keyed by its arguments, so repeated calls with the same input return instantly instead of recomputing. It trades memory for speed and is ideal for pure, expensive functions (recursion, heavy calculations). Closures hold the cache.

💻 Code example
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 slowSquare = n => { /* heavy */ return n * n; };
const fast = memoize(slowSquare);
fast(5); // computes → 25
fast(5); // cached → 25 instantly
Q45.What is event bubbling and capturing?Intermediate

When an event fires on an element, it travels through three phases: capturing (top → target, from window down), the target phase, then bubbling (target → top, back up). By default listeners run in the bubbling phase. Pass { capture: true } to listen during capture. event.stopPropagation() halts further travel.

💻 Code example
// Bubbling (default): inner fires first, then outer
outer.addEventListener("click", () => console.log("outer"));
inner.addEventListener("click", () => console.log("inner"));
// Click inner → "inner", "outer"

// Capturing: outer fires first
outer.addEventListener("click", () => console.log("capture"),
  { capture: true });

// Stop bubbling
inner.addEventListener("click", e => e.stopPropagation());
Q46.What is the difference between slice and splice?Intermediate

slice(start, end) returns a shallow copy of a portion of an array WITHOUT modifying the original (non-mutating). splice(start, deleteCount, ...items) CHANGES the original array by removing/replacing/inserting elements and returns the removed elements. Mnemonic: 'splice' mutates (it has the 'p' for 'permanent change').

💻 Code example
const arr = [1, 2, 3, 4, 5];

// slice — non-mutating
const part = arr.slice(1, 3);   // [2, 3]
console.log(arr);               // [1,2,3,4,5] (unchanged)

// splice — mutating
const removed = arr.splice(1, 2, "a", "b");
console.log(removed);           // [2, 3] (removed)
console.log(arr);               // [1, "a", "b", 4, 5]
Q47.How do you remove duplicates from an array?Intermediate

The cleanest way is to pass the array to a Set (which keeps only unique values) and spread it back into an array. Alternatives include filter with indexOf (O(n²)) or reduce. For arrays of objects, dedupe by a key using a Map.

💻 Code example
const arr = [1, 2, 2, 3, 3, 4];

// Best — Set
const unique = [...new Set(arr)];          // [1, 2, 3, 4]

// filter + indexOf (slower)
const u2 = arr.filter((v, i) => arr.indexOf(v) === i);

// Objects by key
const people = [{ id: 1 }, { id: 1 }, { id: 2 }];
const byId = [...new Map(people.map(p => [p.id, p])).values()];
Q48.What is the difference between push/pop and shift/unshift?Beginner

push adds to the END and pop removes from the END (stack, LIFO) — both O(1). unshift adds to the START and shift removes from the START (queue, FIFO) — both O(n) because every element must be re-indexed. All four mutate the original array and return either the new length (push/unshift) or the removed element (pop/shift).

💻 Code example
const arr = [2, 3];

arr.push(4);     // [2, 3, 4]     — returns 3 (new length)
arr.pop();       // [2, 3]        — returns 4 (removed)
arr.unshift(1);  // [1, 2, 3]     — returns 3 (new length)
arr.shift();     // [2, 3]        — returns 1 (removed)
Q49.What does 'use strict' do in JavaScript?Intermediate

'use strict' enables strict mode, a restricted variant of JavaScript that catches common mistakes by throwing errors: assigning to undeclared variables, duplicate parameter names, writing to read-only properties, and using reserved keywords. It also makes this undefined in plain function calls (instead of the global object). ES modules and class bodies are strict by default.

💻 Code example
"use strict";

x = 10;          // ReferenceError: x is not defined
                 // (without strict mode, x becomes global)

function f() {
  return this;   // undefined (not window) in strict mode
}

// Duplicate params, deleting variables, etc. also throw
Q50.How does garbage collection work in JavaScript?Advanced

JavaScript automatically frees memory using a mark-and-sweep garbage collector: starting from roots (global object, current call stack), it marks every reachable object, then sweeps away anything unreachable. You don't free memory manually. Memory leaks still happen via lingering references — forgotten timers, detached DOM nodes, growing global caches, and closures that outlive their usefulness.

💻 Code example
let user = { name: "Sam" };
user = null;   // old object now unreachable → eligible for GC

// Common leak: a closure keeps a big object alive
function leak() {
  const big = new Array(1e6).fill("x");
  return () => big[0];   // closure retains `big` forever
}
const held = leak();     // big can't be collected while `held` exists

📚 Want to go deeper?

Pair these questions with real implementation practice from our free courses: