Navigation

GyaanSetu Educational Platform

Top 20 JavaScript Interview Questions for 2026

Master closures, promises, async/await, ES6+ features, and modern JavaScript concepts

ES6+ FeaturesAsync/AwaitClosuresEvent Loop
1

What's the difference between var, let, and const?

var is function-scoped, can be redeclared, and hoisted with undefined. let is block-scoped, cannot be redeclared in the same scope, and hoisted but not initialized (temporal dead zone). const is block-scoped, must be initialized at declaration, cannot be reassigned (but objects/arrays can be mutated), and also has temporal dead zone. Modern code prefers const by default, let when reassignment is needed, and avoids var.

example.js
1// var - function scoped, can be redeclared
2var x = 10;
3var x = 20; // OK
4if (true) {
5 var x = 30; // Same variable
6}
7console.log(x); // 30
8
9// let - block scoped, cannot be redeclared
10let y = 10;
11// let y = 20; // Error: already declared
12if (true) {
13 let y = 30; // Different variable
14 console.log(y); // 30
15}
16console.log(y); // 10
17
18// const - block scoped, cannot be reassigned
19const z = 10;
20// z = 20; // Error: assignment to constant
21const obj = { name: "John" };
22obj.name = "Jane"; // OK - object mutation allowed
23// obj = {}; // Error: reassignment not allowed
2

Explain closures and give a practical use case.

A closure is a function that has access to variables in its outer (enclosing) function's scope, even after the outer function has returned. Closures are created every time a function is created. Practical uses include: data privacy/encapsulation, function factories, callbacks, and maintaining state in async operations. They're fundamental to JavaScript's functional programming capabilities.

example.js
1// Basic closure example
2function outerFunction(outerVar) {
3 return function innerFunction(innerVar) {
4 console.log(`Outer: ${outerVar}, Inner: ${innerVar}`);
5 };
6}
7
8const newFunction = outerFunction("outside");
9newFunction("inside"); // Outer: outside, Inner: inside
10
11// Practical use case: Private counter
12function createCounter() {
13 let count = 0; // Private variable
14
15 return {
16 increment: () => ++count,
17 decrement: () => --count,
18 getCount: () => count
19 };
20}
21
22const counter = createCounter();
23console.log(counter.increment()); // 1
24console.log(counter.increment()); // 2
25console.log(counter.getCount()); // 2
26// console.log(counter.count); // undefined - private!
3

What are Promises and async/await?

Promises represent the eventual completion or failure of an asynchronous operation. They have three states: pending, fulfilled, or rejected. Promises use .then() for success and .catch() for errors. async/await is syntactic sugar over Promises, making asynchronous code look synchronous. An async function always returns a Promise, and await pauses execution until the Promise resolves. This makes code more readable and easier to debug than chaining .then().

example.js
1// Promise example
2function fetchUser(id) {
3 return new Promise((resolve, reject) => {
4 setTimeout(() => {
5 if (id > 0) {
6 resolve({ id, name: "John Doe" });
7 } else {
8 reject(new Error("Invalid ID"));
9 }
10 }, 1000);
11 });
12}
13
14// Using .then()
15fetchUser(1)
16 .then(user => console.log(user))
17 .catch(error => console.error(error));
18
19// Using async/await (cleaner!)
20async function getUser(id) {
21 try {
22 const user = await fetchUser(id);
23 console.log(user);
24 return user;
25 } catch (error) {
26 console.error(error);
27 }
28}
29
30// Promise.all for parallel execution
31async function getAllUsers() {
32 const [user1, user2, user3] = await Promise.all([
33 fetchUser(1),
34 fetchUser(2),
35 fetchUser(3)
36 ]);
37 return [user1, user2, user3];
38}
4

What is hoisting and how does it work with functions and variables?

Hoisting is JavaScript's behavior of moving declarations to the top of their scope before code execution. Function declarations are fully hoisted (can be called before declaration). var variables are hoisted but initialized with undefined. let and const are hoisted but not initialized, creating a 'temporal dead zone' where accessing them before declaration throws a ReferenceError. Function expressions and arrow functions are not hoisted like function declarations.

example.js
1// Function hoisting - works!
2sayHello(); // "Hello!"
3function sayHello() {
4 console.log("Hello!");
5}
6
7// var hoisting - undefined
8console.log(x); // undefined (not error)
9var x = 5;
10console.log(x); // 5
11
12// let/const - temporal dead zone
13// console.log(y); // ReferenceError
14let y = 10;
15
16// Function expression - NOT hoisted
17// greet(); // TypeError: greet is not a function
18var greet = function() {
19 console.log("Hi!");
20};
21
22// Arrow function - NOT hoisted
23// sayBye(); // ReferenceError
24const sayBye = () => console.log("Bye!");
25
26// What actually happens:
27var x; // Declaration hoisted
28console.log(x); // undefined
29x = 5; // Assignment stays in place
5

Explain the difference between == and ===.

== (loose equality) compares values after type coercion - it converts operands to the same type before comparison. === (strict equality) compares both value and type without coercion. === is generally preferred as it's more predictable and avoids unexpected type conversions. For example, 0 == false is true (coercion), but 0 === false is false (different types).

example.js
1// == performs type coercion
2console.log(5 == "5"); // true (string converted to number)
3console.log(0 == false); // true
4console.log(null == undefined); // true
5console.log("" == 0); // true
6console.log([1] == 1); // true
7
8// === no type coercion (strict)
9console.log(5 === "5"); // false (different types)
10console.log(0 === false); // false
11console.log(null === undefined); // false
12console.log("" === 0); // false
13console.log([1] === 1); // false
14
15// Unexpected behavior with ==
16console.log("0" == false); // true
17console.log("0" == 0); // true
18console.log(false == 0); // true
19console.log(false == ""); // true
20
21// Always use === unless you specifically need coercion
22const userInput = "123";
23if (userInput === "123") { // Explicit string check
24 console.log("Match!");
25}
6

Explain map, reduce, filter, and find array methods.

map() creates a new array by transforming each element. filter() creates a new array with elements that pass a test. reduce() reduces an array to a single value by accumulating results. find() returns the first element that matches a condition (or undefined). These are functional programming methods that don't mutate the original array. They're essential for working with data in modern JavaScript.

example.js
1const numbers = [1, 2, 3, 4, 5];
2const users = [
3 { id: 1, name: "John", age: 25, active: true },
4 { id: 2, name: "Jane", age: 30, active: false },
5 { id: 3, name: "Bob", age: 35, active: true }
6];
7
8// map - transform each element
9const doubled = numbers.map(n => n * 2);
10console.log(doubled); // [2, 4, 6, 8, 10]
11
12const names = users.map(u => u.name);
13console.log(names); // ["John", "Jane", "Bob"]
14
15// filter - keep elements that pass test
16const evens = numbers.filter(n => n % 2 === 0);
17console.log(evens); // [2, 4]
18
19const activeUsers = users.filter(u => u.active);
20console.log(activeUsers); // [John, Bob]
21
22// reduce - accumulate to single value
23const sum = numbers.reduce((acc, n) => acc + n, 0);
24console.log(sum); // 15
25
26const totalAge = users.reduce((acc, u) => acc + u.age, 0);
27console.log(totalAge); // 90
28
29// find - first matching element
30const found = numbers.find(n => n > 3);
31console.log(found); // 4
32
33const user = users.find(u => u.id === 2);
34console.log(user); // { id: 2, name: "Jane", ... }
7

What's the difference between while and do-while loops?

A while loop checks the condition before executing the code block - if the condition is false initially, the code never runs. A do-while loop executes the code block first, then checks the condition - guaranteeing at least one execution. Use while when you want to check before execution, do-while when you need to execute at least once (like user input validation).

example.js
1// while - condition checked first
2let i = 0;
3while (i < 3) {
4 console.log(`while: ${i}`);
5 i++;
6}
7// Output: while: 0, while: 1, while: 2
8
9// If condition false, never executes
10let x = 5;
11while (x < 3) {
12 console.log("This never runs");
13}
14
15// do-while - executes first, then checks
16let j = 0;
17do {
18 console.log(`do-while: ${j}`);
19 j++;
20} while (j < 3);
21// Output: do-while: 0, do-while: 1, do-while: 2
22
23// Executes at least once even if condition false
24let y = 5;
25do {
26 console.log(`This runs once: ${y}`);
27} while (y < 3);
28// Output: This runs once: 5
29
30// Practical use case: user input
31let userInput;
32do {
33 userInput = prompt("Enter a number > 10:");
34} while (userInput <= 10); // Keep asking until valid
8

What is debouncing and throttling? When would you use them?

Debouncing delays function execution until after a specified time has passed since the last call - useful for search inputs, window resize. Throttling limits function execution to once per specified time period - useful for scroll events, mouse movements. Debouncing waits for inactivity, throttling ensures regular execution. Both optimize performance by reducing function calls.

example.js
1// Debouncing - execute after user stops typing
2function debounce(func, delay) {
3 let timeoutId;
4 return function(...args) {
5 clearTimeout(timeoutId);
6 timeoutId = setTimeout(() => {
7 func.apply(this, args);
8 }, delay);
9 };
10}
11
12// Usage: Search input
13const searchAPI = (query) => {
14 console.log(`Searching for: ${query}`);
15 // API call here
16};
17
18const debouncedSearch = debounce(searchAPI, 500);
19// User types: only calls API 500ms after they stop typing
20input.addEventListener("input", (e) => {
21 debouncedSearch(e.target.value);
22});
23
24// Throttling - execute at regular intervals
25function throttle(func, limit) {
26 let inThrottle;
27 return function(...args) {
28 if (!inThrottle) {
29 func.apply(this, args);
30 inThrottle = true;
31 setTimeout(() => inThrottle = false, limit);
32 }
33 };
34}
35
36// Usage: Scroll event
37const handleScroll = () => {
38 console.log("Scroll position:", window.scrollY);
39};
40
41const throttledScroll = throttle(handleScroll, 1000);
42// Executes max once per second during scrolling
43window.addEventListener("scroll", throttledScroll);
9

What's the difference between shallow copy and deep copy in JavaScript?

Shallow copy creates a new object but copies references to nested objects - changes to nested objects affect both copies. Deep copy creates completely independent copies including all nested objects - changes don't affect the original. Spread operator (...) and Object.assign() create shallow copies. For deep copies, use JSON.parse(JSON.stringify()) (loses functions/dates) or libraries like Lodash's cloneDeep(), or structuredClone() (modern browsers).

example.js
1const original = {
2 name: "John",
3 age: 30,
4 address: {
5 city: "NYC",
6 zip: 10001
7 }
8};
9
10// Shallow copy - nested objects are referenced
11const shallow1 = { ...original };
12const shallow2 = Object.assign({}, original);
13
14shallow1.name = "Jane"; // OK - primitive copied
15shallow1.address.city = "LA"; // Affects original!
16
17console.log(original.address.city); // "LA" - changed!
18console.log(shallow1.address.city); // "LA"
19
20// Deep copy methods
21// Method 1: JSON (loses functions, dates, undefined)
22const deep1 = JSON.parse(JSON.stringify(original));
23deep1.address.city = "Boston";
24console.log(original.address.city); // "NYC" - unchanged!
25
26// Method 2: structuredClone (modern, recommended)
27const deep2 = structuredClone(original);
28deep2.address.zip = 20001;
29console.log(original.address.zip); // 10001 - unchanged!
30
31// Shallow copy of arrays
32const arr = [[1, 2], [3, 4]];
33const shallowArr = [...arr];
34shallowArr[0][0] = 99; // Affects original!
35console.log(arr[0][0]); // 99
36
37const deepArr = structuredClone(arr);
38deepArr[0][0] = 88; // Doesn't affect original
39console.log(arr[0][0]); // 99
10

What is the event loop and how does JavaScript handle asynchronous code?

The event loop is JavaScript's mechanism for handling asynchronous operations in a single-threaded environment. It continuously checks the call stack and task queues. Synchronous code executes on the call stack. Async operations (setTimeout, Promises, I/O) are handled by Web APIs and placed in task queues. When the call stack is empty, the event loop moves tasks from queues to the stack. Microtasks (Promises) have higher priority than macrotasks (setTimeout).

example.js
1console.log("1. Start");
2
3setTimeout(() => {
4 console.log("2. setTimeout (macrotask)");
5}, 0);
6
7Promise.resolve().then(() => {
8 console.log("3. Promise (microtask)");
9});
10
11console.log("4. End");
12
13// Output order:
14// 1. Start
15// 4. End
16// 3. Promise (microtask) - microtasks run first!
17// 2. setTimeout (macrotask)
18
19// More complex example
20console.log("A");
21
22setTimeout(() => console.log("B"), 0);
23
24Promise.resolve()
25 .then(() => console.log("C"))
26 .then(() => console.log("D"));
27
28setTimeout(() => console.log("E"), 0);
29
30console.log("F");
31
32// Output: A, F, C, D, B, E
33// Explanation:
34// 1. Sync code: A, F
35// 2. Microtasks (Promises): C, D
36// 3. Macrotasks (setTimeout): B, E
11

Explain 'this' keyword and how it works in different contexts.

The 'this' keyword refers to the context in which a function is executed. In regular functions, 'this' depends on how the function is called: in methods it's the object, in regular functions it's window/undefined (strict), in constructors/classes it's the new instance. Arrow functions don't have their own 'this' - they inherit from the enclosing scope. call(), apply(), and bind() can explicitly set 'this'.

example.js
1// Global context
2console.log(this); // window (browser) or global (Node.js)
3
4// Object method
5const person = {
6 name: "John",
7 greet: function() {
8 console.log(`Hello, ${this.name}`);
9 },
10 greetArrow: () => {
11 console.log(`Hi, ${this.name}`); // 'this' is window!
12 }
13};
14
15person.greet(); // "Hello, John" - 'this' is person
16person.greetArrow(); // "Hi, undefined" - arrow function!
17
18// Constructor function
19function Person(name) {
20 this.name = name;
21 this.sayHi = function() {
22 console.log(`Hi, I'm ${this.name}`);
23 };
24}
25
26const john = new Person("John");
27john.sayHi(); // "Hi, I'm John"
28
29// Losing context
30const greetFunc = person.greet;
31greetFunc(); // "Hello, undefined" - 'this' is window!
32
33// Binding context
34const boundGreet = person.greet.bind(person);
35boundGreet(); // "Hello, John" - 'this' bound to person
36
37// call() and apply()
38function introduce(age, city) {
39 console.log(`${this.name}, ${age}, ${city}`);
40}
41
42introduce.call(person, 30, "NYC"); // John, 30, NYC
43introduce.apply(person, [30, "NYC"]); // Same result
12

What are arrow functions and how do they differ from regular functions?

Arrow functions are a concise syntax for writing functions. Key differences: no 'this' binding (inherit from parent scope), no 'arguments' object, can't be used as constructors (no 'new'), implicit return for single expressions, can't be used as generators. They're ideal for callbacks, array methods, and when you want to preserve 'this' context. Use regular functions for methods and constructors.

example.js
1// Regular function vs Arrow function
2const regular = function(a, b) {
3 return a + b;
4};
5
6const arrow = (a, b) => a + b; // Implicit return
7
8// Multiple parameters
9const add = (a, b) => a + b;
10
11// Single parameter (parentheses optional)
12const square = x => x * x;
13const squareAlt = (x) => x * x;
14
15// No parameters
16const greet = () => console.log("Hello!");
17
18// Multi-line (explicit return needed)
19const multiply = (a, b) => {
20 const result = a * b;
21 return result;
22};
23
24// 'this' binding difference
25const obj = {
26 name: "Object",
27 regularFunc: function() {
28 console.log(this.name); // "Object"
29 },
30 arrowFunc: () => {
31 console.log(this.name); // undefined (inherits from parent)
32 }
33};
34
35// No 'arguments' object in arrow functions
36function regularArgs() {
37 console.log(arguments); // [1, 2, 3]
38}
39regularArgs(1, 2, 3);
40
41const arrowArgs = () => {
42 // console.log(arguments); // ReferenceError
43};
44
45// Use rest parameters instead
46const arrowRest = (...args) => {
47 console.log(args); // [1, 2, 3]
48};
49arrowRest(1, 2, 3);
13

What is destructuring in JavaScript?

Destructuring is a syntax for extracting values from arrays or properties from objects into distinct variables. It makes code cleaner and more readable. Array destructuring uses position-based extraction with []. Object destructuring uses property names with {}. You can set default values, rename variables, skip elements, and use rest operator. It's commonly used with function parameters, imports, and working with API responses.

example.js
1// Array destructuring
2const colors = ["red", "green", "blue", "yellow"];
3const [first, second, ...rest] = colors;
4console.log(first); // "red"
5console.log(second); // "green"
6console.log(rest); // ["blue", "yellow"]
7
8// Skip elements
9const [, , third] = colors;
10console.log(third); // "blue"
11
12// Default values
13const [a, b, c, d, e = "purple"] = colors;
14console.log(e); // "purple"
15
16// Object destructuring
17const user = {
18 id: 1,
19 name: "John Doe",
20 email: "john@example.com",
21 age: 30
22};
23
24const { name, email } = user;
25console.log(name); // "John Doe"
26
27// Rename variables
28const { name: userName, age: userAge } = user;
29console.log(userName); // "John Doe"
30
31// Default values
32const { phone = "N/A" } = user;
33console.log(phone); // "N/A"
34
35// Nested destructuring
36const person = {
37 name: "Jane",
38 address: {
39 city: "NYC",
40 zip: 10001
41 }
42};
43
44const { address: { city, zip } } = person;
45console.log(city); // "NYC"
46
47// Function parameters
48function displayUser({ name, age, email = "N/A" }) {
49 console.log(`${name}, ${age}, ${email}`);
50}
51
52displayUser(user); // "John Doe, 30, john@example.com"
14

What are template literals and what are their benefits?

Template literals (template strings) use backticks (``) instead of quotes and allow embedded expressions using ${expression}. Benefits: multi-line strings without \n, expression interpolation, cleaner concatenation, tagged templates for custom processing. They make string manipulation more readable and maintainable. Commonly used for HTML templates, dynamic messages, and formatting output.

example.js
1// Basic template literal
2const name = "John";
3const age = 30;
4
5// Old way (concatenation)
6const oldWay = "Hello, my name is " + name + " and I'm " + age + " years old.";
7
8// Template literal way (cleaner!)
9const newWay = `Hello, my name is ${name} and I'm ${age} years old.`;
10
11// Multi-line strings
12const multiLine = `
13 This is line 1
14 This is line 2
15 This is line 3
16`;
17
18// Expressions in template literals
19const a = 10;
20const b = 20;
21console.log(`Sum: ${a + b}`); // "Sum: 30"
22
23// Function calls
24function getGreeting() {
25 return "Hello";
26}
27console.log(`${getGreeting()}, ${name}!`); // "Hello, John!"
28
29// HTML template
30const createCard = (title, content) => `
31 <div class="card">
32 <h2>${title}</h2>
33 <p>${content}</p>
34 <span>Posted: ${new Date().toLocaleDateString()}</span>
35 </div>
36`;
37
38// Tagged templates (advanced)
39function highlight(strings, ...values) {
40 return strings.reduce((result, str, i) => {
41 return `${result}${str}<mark>${values[i] || ""}</mark>`;
42 }, "");
43}
44
45const highlighted = highlight`My name is ${name} and I'm ${age} years old`;
46// "My name is <mark>John</mark> and I'm <mark>30</mark> years old"
15

What is the spread operator and rest parameters?

The spread operator (...) expands iterables (arrays, strings, objects) into individual elements. Used for copying arrays/objects, merging, passing array elements as function arguments. Rest parameters (...) collect multiple arguments into an array. Same syntax (...) but opposite purposes: spread expands, rest collects. Spread is used in expressions, rest in function parameters and destructuring.

example.js
1// Spread operator - expanding elements
2const arr1 = [1, 2, 3];
3const arr2 = [4, 5, 6];
4
5// Merge arrays
6const merged = [...arr1, ...arr2];
7console.log(merged); // [1, 2, 3, 4, 5, 6]
8
9// Copy array (shallow)
10const copy = [...arr1];
11copy.push(4);
12console.log(arr1); // [1, 2, 3] - original unchanged
13
14// Spread in function calls
15const numbers = [1, 5, 3, 9, 2];
16console.log(Math.max(...numbers)); // 9
17
18// Spread with objects
19const user = { name: "John", age: 30 };
20const updatedUser = { ...user, age: 31, city: "NYC" };
21console.log(updatedUser); // { name: "John", age: 31, city: "NYC" }
22
23// Rest parameters - collecting arguments
24function sum(...numbers) {
25 return numbers.reduce((acc, n) => acc + n, 0);
26}
27
28console.log(sum(1, 2, 3, 4, 5)); // 15
29
30// Rest with other parameters
31function multiply(multiplier, ...numbers) {
32 return numbers.map(n => n * multiplier);
33}
34
35console.log(multiply(2, 1, 2, 3)); // [2, 4, 6]
36
37// Rest in destructuring
38const [first, second, ...rest] = [1, 2, 3, 4, 5];
39console.log(rest); // [3, 4, 5]
40
41const { name, ...otherProps } = user;
42console.log(otherProps); // { age: 30 }
16

What are JavaScript modules (import/export)?

Modules allow you to split code into separate files and import/export functionality between them. Named exports use 'export' keyword and import with {}. Default exports use 'export default' and import without {}. Benefits: code organization, reusability, namespace management, lazy loading. Modules have their own scope, are in strict mode by default, and execute only once. Modern JavaScript uses ES6 modules (import/export) over CommonJS (require/module.exports).

example.js
1// math.js - Named exports
2export const add = (a, b) => a + b;
3export const subtract = (a, b) => a - b;
4
5export function multiply(a, b) {
6 return a * b;
7}
8
9// user.js - Default export
10const user = {
11 name: "John",
12 age: 30
13};
14
15export default user;
16
17// Can combine default and named
18export const formatName = (name) => name.toUpperCase();
19
20// app.js - Importing
21import user from "./user.js"; // Default import
22import { add, subtract } from "./math.js"; // Named imports
23import { multiply as mult } from "./math.js"; // Rename
24import * as MathOps from "./math.js"; // Import all
25
26console.log(user.name);
27console.log(add(5, 3));
28console.log(MathOps.multiply(4, 2));
29
30// Dynamic imports (lazy loading)
31async function loadModule() {
32 const module = await import("./heavy-module.js");
33 module.doSomething();
34}
35
36// Re-exporting
37export { add, subtract } from "./math.js";
38export * from "./utils.js";
39
40// CommonJS (older, Node.js)
41// const add = require('./math');
42// module.exports = { add };
17

What is the difference between null and undefined?

undefined means a variable has been declared but not assigned a value, or a function doesn't return anything. null is an explicit assignment representing 'no value' or 'empty'. typeof undefined is 'undefined', typeof null is 'object' (historical bug). undefined is the default for uninitialized variables, null must be explicitly assigned. In comparisons: null == undefined (true), but null === undefined (false).

example.js
1// undefined - variable declared but not assigned
2let x;
3console.log(x); // undefined
4console.log(typeof x); // "undefined"
5
6// Function with no return
7function noReturn() {
8 // no return statement
9}
10console.log(noReturn()); // undefined
11
12// Missing object property
13const obj = { name: "John" };
14console.log(obj.age); // undefined
15
16// Missing array element
17const arr = [1, 2, 3];
18console.log(arr[10]); // undefined
19
20// null - explicit assignment of "no value"
21let y = null;
22console.log(y); // null
23console.log(typeof y); // "object" (historical bug!)
24
25// null usage - intentionally empty
26let user = null; // No user logged in
27let data = null; // No data loaded yet
28
29// Checking for null/undefined
30if (x === undefined) {
31 console.log("x is undefined");
32}
33
34if (y === null) {
35 console.log("y is null");
36}
37
38// Nullish coalescing (??) - null or undefined
39const value1 = null ?? "default"; // "default"
40const value2 = undefined ?? "default"; // "default"
41const value3 = 0 ?? "default"; // 0 (not null/undefined)
42const value4 = "" ?? "default"; // "" (not null/undefined)
43
44// Optional chaining (?.)
45const user2 = { name: "John" };
46console.log(user2?.address?.city); // undefined (no error)
18

What are higher-order functions?

Higher-order functions are functions that take other functions as arguments or return functions as results. They enable functional programming patterns like composition, currying, and callbacks. Common examples: map, filter, reduce, forEach. Benefits include code reusability, abstraction, and more declarative code. They're fundamental to JavaScript's functional programming capabilities and are widely used in modern frameworks and libraries.

example.js
1// Function that takes a function as argument
2function executeOperation(a, b, operation) {
3 return operation(a, b);
4}
5
6const add = (x, y) => x + y;
7const multiply = (x, y) => x * y;
8
9console.log(executeOperation(5, 3, add)); // 8
10console.log(executeOperation(5, 3, multiply)); // 15
11
12// Function that returns a function
13function createMultiplier(multiplier) {
14 return function(number) {
15 return number * multiplier;
16 };
17}
18
19const double = createMultiplier(2);
20const triple = createMultiplier(3);
21
22console.log(double(5)); // 10
23console.log(triple(5)); // 15
24
25// Practical example: Array methods
26const numbers = [1, 2, 3, 4, 5];
27
28// map is a higher-order function
29const doubled = numbers.map(n => n * 2);
30
31// Custom higher-order function
32function customMap(array, transformFn) {
33 const result = [];
34 for (let item of array) {
35 result.push(transformFn(item));
36 }
37 return result;
38}
39
40const squared = customMap(numbers, n => n * n);
41console.log(squared); // [1, 4, 9, 16, 25]
42
43// Currying with higher-order functions
44const curry = (fn) => {
45 return function curried(...args) {
46 if (args.length >= fn.length) {
47 return fn.apply(this, args);
48 } else {
49 return (...nextArgs) => {
50 return curried.apply(this, [...args, ...nextArgs]);
51 };
52 }
53 };
54};
55
56const sum = (a, b, c) => a + b + c;
57const curriedSum = curry(sum);
58console.log(curriedSum(1)(2)(3)); // 6
59console.log(curriedSum(1, 2)(3)); // 6
19

What is event bubbling and event capturing?

Event propagation has three phases: capturing (top to target), target, and bubbling (target to top). Event bubbling is when an event on a child element propagates up to parent elements. Event capturing is the opposite - event travels from root to target. By default, event listeners use bubbling phase. Use event.stopPropagation() to stop propagation. event.preventDefault() prevents default browser behavior. Understanding this is crucial for event delegation.

example.js
1// HTML structure:
2// <div id="outer">
3// <div id="middle">
4// <button id="inner">Click</button>
5// </div>
6// </div>
7
8// Event Bubbling (default)
9document.getElementById("outer").addEventListener("click", () => {
10 console.log("Outer clicked");
11});
12
13document.getElementById("middle").addEventListener("click", () => {
14 console.log("Middle clicked");
15});
16
17document.getElementById("inner").addEventListener("click", () => {
18 console.log("Inner clicked");
19});
20
21// Click button output:
22// Inner clicked
23// Middle clicked
24// Outer clicked
25
26// Event Capturing (useCapture: true)
27document.getElementById("outer").addEventListener("click", () => {
28 console.log("Outer (capturing)");
29}, true);
30
31document.getElementById("middle").addEventListener("click", () => {
32 console.log("Middle (capturing)");
33}, true);
34
35// Output with capturing:
36// Outer (capturing)
37// Middle (capturing)
38// Inner clicked
39
40// Stop propagation
41document.getElementById("inner").addEventListener("click", (e) => {
42 e.stopPropagation(); // Stops bubbling
43 console.log("Inner clicked only");
44});
45
46// Prevent default behavior
47document.getElementById("link").addEventListener("click", (e) => {
48 e.preventDefault(); // Don't follow link
49 console.log("Link clicked but not followed");
50});
51
52// Event delegation (using bubbling)
53document.getElementById("list").addEventListener("click", (e) => {
54 if (e.target.tagName === "LI") {
55 console.log("List item clicked:", e.target.textContent);
56 }
57});
20

What are JavaScript Promises methods: all, race, allSettled, any?

Promise.all() waits for all promises to resolve (or any to reject) - returns array of results, fails fast on first rejection. Promise.race() returns first settled promise (resolved or rejected). Promise.allSettled() waits for all promises to settle (resolve or reject) - always succeeds, returns array of status objects. Promise.any() returns first fulfilled promise, ignores rejections unless all reject. Each serves different use cases for handling multiple async operations.

example.js
1// Promise.all - wait for all, fail fast
2const promise1 = Promise.resolve(1);
3const promise2 = Promise.resolve(2);
4const promise3 = Promise.resolve(3);
5
6Promise.all([promise1, promise2, promise3])
7 .then(results => console.log(results)) // [1, 2, 3]
8 .catch(error => console.error(error));
9
10// If any fails, entire Promise.all fails
11const failing = Promise.all([
12 Promise.resolve(1),
13 Promise.reject("Error!"),
14 Promise.resolve(3)
15]); // Rejects with "Error!"
16
17// Promise.race - first to settle wins
18Promise.race([
19 new Promise(resolve => setTimeout(() => resolve("slow"), 2000)),
20 new Promise(resolve => setTimeout(() => resolve("fast"), 100))
21]).then(result => console.log(result)); // "fast"
22
23// Use case: timeout
24const fetchWithTimeout = (url, timeout) => {
25 return Promise.race([
26 fetch(url),
27 new Promise((_, reject) =>
28 setTimeout(() => reject("Timeout"), timeout)
29 )
30 ]);
31};
32
33// Promise.allSettled - wait for all, never fails
34Promise.allSettled([
35 Promise.resolve(1),
36 Promise.reject("Error"),
37 Promise.resolve(3)
38]).then(results => {
39 console.log(results);
40 // [
41 // { status: "fulfilled", value: 1 },
42 // { status: "rejected", reason: "Error" },
43 // { status: "fulfilled", value: 3 }
44 // ]
45});
46
47// Promise.any - first successful, ignore failures
48Promise.any([
49 Promise.reject("Error 1"),
50 Promise.resolve("Success!"),
51 Promise.reject("Error 2")
52]).then(result => console.log(result)); // "Success!"
53
54// If all reject
55Promise.any([
56 Promise.reject("Error 1"),
57 Promise.reject("Error 2")
58]).catch(error => {
59 console.log(error); // AggregateError: All promises rejected
60});

Ready for More Interview Prep?

Explore our complete collection of frontend interview questions covering CSS, React, HTML, and more.

View All Topics

© 2026 GyaanSetu. Helping developers ace their interviews.