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.
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'.
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 (===).
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.
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.
"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.
// 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).
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.
// 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).
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); // unchangedScope, 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.
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.
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.
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.
// 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, 2Q13.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.
{
// 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.
(function () {
const secret = 42; // not global
console.log(secret); // 42
})();
// Arrow IIFE
(() => {
console.log("runs once");
})();
// console.log(secret); // ReferenceError — scope is privateFunctions & 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.
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.
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.
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 constructorQ18.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.
// 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); // 15Q19.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.
// 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); // 60Q20.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.
// 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.
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.
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); // trueQ23.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.
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)); // trueQ24.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.
// 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=1Q25.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.
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.
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.
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.
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.
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.
// 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).
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).
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.
// 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 totalES6+ 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.
// 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); // 6Q35.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.
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.
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.
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.
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.
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); // chainableQ40.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.
// 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.14159Advanced & 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.
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.
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 200msQ43.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.
// 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.
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 instantlyQ45.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.
// 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').
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.
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).
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.
"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 throwQ50.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.
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: