interviewDeck

Your one-stop platform to prepare, practice and ace your interviews.

Loading your questions…

All Questions

Filters & tools

JavaScript Interview Questions and Answers

64 hand-picked JavaScript interview questions with detailed answers. Open the interactive version above to search, filter by difficulty, run code, bookmark questions and track your progress.

What is a closure? Give a real use.

A closure is a function that keeps access to variables from its outer (lexical) scope even after the outer function has returned. The inner function "remembers" the environment it was created in.

Uses: data privacy (private variables), function factories, and holding state in callbacks — e.g. a debounce keeps its timer id in a closure.

function counter() { let n = 0; return () => ++n; }
const next = counter();
next(); // 1  → n survives but stays private

var vs let vs const, and what is hoisting?

  • var — function-scoped, hoisted and initialised to undefined, re-declarable.
  • let — block-scoped, hoisted but in the Temporal Dead Zone (error if used before declaration).
  • const — block-scoped, assigned once. The binding is constant, not the object — you can still mutate a const array/object.

Hoisting: declarations are moved to the top of their scope at compile time. var and function declarations are usable early; let/const are not.

Explain the event loop, microtasks vs macrotasks.

JS is single-threaded. The call stack runs synchronous code. Async callbacks wait in queues and the event loop pushes them onto the stack when it's empty.

  • Microtask queue — Promise .then, queueMicrotask, await continuations. Fully drained after each task.
  • Macrotask queuesetTimeout, setInterval, I/O, UI events.

Microtasks always run before the next macrotask.

console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);
// → 1, 4, 3, 2

== vs === ?

== compares after type coercion (0 == "0" is true). === checks value and type with no coercion.

Always use === unless you deliberately want coercion (e.g. x == null to catch both null and undefined).

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

How does the this keyword work?

this depends on how a function is called:

  • Regular call → global object, or undefined in strict mode.
  • Method call obj.fn()obj.
  • Arrow function → no own this; inherits from the enclosing scope.
  • call / apply / bind → set this explicitly.
const obj = {
  name: 'A',
  greet() { setTimeout(() => console.log(this.name), 0); }
};
obj.greet(); // 'A' — arrow keeps this

Promise vs async/await, and how do you handle errors?

A Promise represents a future value (pending → fulfilled/rejected). async/await is syntactic sugar that lets asynchronous code read synchronously.

Errors: .catch() on a chain, or try/catch around await. Use Promise.all for parallel and Promise.allSettled when you need every result regardless of failures.

async function load() {
  try {
    const res = await fetch('/api/user');
    return await res.json();
  } catch (e) { /* handle */ }
}

Debounce vs throttle — what and when?

Both limit how often a function runs, but differently:

  • Debounce — waits until events stop for N ms, then runs once (each new event resets the timer). Best for search boxes, autocomplete, validation.
  • Throttle — runs at most once every N ms, no matter how many events fire. Best for scroll, resize, drag, mousemove.
// Debounce
function debounce(fn, delay) {
  let timer;

  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn(...args);
    }, delay);
  };
}

// Throttle
function throttle(fn, delay) {
  let lastCall = 0;

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

    if (now - lastCall >= delay) {
      lastCall = now;
      fn(...args);
    }
  };
}

map vs forEach vs reduce.

All three iterate an array, but return different things:

  • map — returns a new array of the same length with each item transformed.
  • forEach — just runs a function per item and returns undefined (used for side effects).
  • reduce — folds the whole array into a single value (a sum, an object, a grouped map).

Rule of thumb: transforming → map; just doing something → forEach; combining into one result → reduce.

const nums = [1, 2, 3];
nums.map(n => n * 2);           // [2, 4, 6]
nums.reduce((a, b) => a + b, 0); // 6

Shallow copy vs deep copy.

  • Shallow copy — a new object, but only the top level is copied; nested objects/arrays are still shared references, so changing them affects both. Made with {...obj}, Object.assign, arr.slice().
  • Deep copy — a fully independent clone, nested values included.

Use structuredClone(obj) for a real deep copy. The old JSON.parse(JSON.stringify(obj)) trick works only for plain JSON and drops Dates, Maps, Sets, functions, and undefined.

const original = {
  name: 'John',
  address: {
    city: 'Bangalore'
  }
};

// Shallow Copy
const shallow = { ...original };
shallow.address.city = 'Mumbai';

console.log(original.address.city); // Mumbai

// Deep Copy
const deep = structuredClone(original);
deep.address.city = 'Delhi';

console.log(original.address.city); // Mumbai
console.log(deep.address.city);     // Delhi

Explain prototypal inheritance.

Prototypal inheritance is how JavaScript objects share properties and methods. Every object has a hidden link ([[Prototype]]) to another object.

  • When you read a property, JS looks on the object first, then walks up the prototype chain until it finds it or reaches null.
  • Shared methods live on the prototype, so all instances reuse one copy.

class syntax is just sugar over this prototype mechanism.

class Animal {
  speak() {
    return 'Some sound';
  }
}

class Dog extends Animal {
  bark() {
    return 'Woof!';
  }
}

const dog = new Dog();

console.log(dog.bark());
console.log(dog.speak());

What is the Temporal Dead Zone (TDZ)?

The span between entering a scope and the point where a let/const variable is declared. The binding is hoisted but not initialised, so accessing it in that window throws a ReferenceError.

console.log(x); // ReferenceError (TDZ)
let x = 5;

Spread vs Rest operator.

Although both use the ... syntax, they perform opposite operations. Spread expands an array or object into individual elements, while Rest collects multiple values into an array or object.

const merged = [...a, ...b];       // Spread

function sum(...nums) {
  return nums.reduce((a, b) => a + b, 0);
}                               // Rest in function parameters

const { id, ...others } = obj;  // Rest in object destructuring

null vs undefined.

undefined — a variable declared but not assigned, or a missing property/return. Set by the engine.

null — an intentional 'no value', set by the developer.

Quirk: typeof null is 'object' (a historical bug).

const name = input ?? 'Guest'; // keeps 0 and '' but replaces null/undefined

What is a higher-order function?

A function that takes a function as an argument and/or returns a function. Examples: map, filter, setTimeout, and any function factory.

const withLog = fn => (...args) => { console.log(args); return fn(...args); };

What is currying?

Currying is a functional programming technique that transforms a function with multiple arguments into a sequence of functions, each taking one argument. Each function remembers its argument using a closure until all required arguments are provided. Currying enables partial application, code reuse, and function composition.

// Curried Function
const add = a => b => c => a + b + c;

console.log(add(1)(2)(3)); // 6

// Partial Application
const multiply = a => b => a * b;

const double = multiply(2);
const triple = multiply(3);

console.log(double(10)); // 20
console.log(triple(10)); // 30

What is event delegation?

Event delegation is a technique where you attach a single event listener to a parent element instead of adding listeners to each child. It works because events bubble from the target element up to its parent, allowing the parent to handle events from its children.

const list = document.getElementById('list');

list.addEventListener('click', (event) => {
  const item = event.target.closest('.item');

  if (item) {
    console.log(item.dataset.id);
  }
});

Event bubbling vs capturing.

When an event fires, it travels capturing phase (top → target) then bubbling phase (target → top). Listeners run in the bubbling phase by default; pass { capture: true } for the capture phase.

stopPropagation() halts travel; preventDefault() cancels the default action.

What is callback hell and how do you avoid it?

Callback Hell is a situation where asynchronous callbacks become deeply nested, making code hard to read and maintain. It is commonly avoided using Promises and async/await.

function getUser(id, callback) {
  setTimeout(() => callback({ id, name: 'John' }), 1000);
}

function getOrders(user, callback) {
  setTimeout(() => callback(['Laptop', 'Phone']), 1000);
}

function getPayment(orders, callback) {
  setTimeout(() => callback('Payment Successful'), 1000);
}

// Callback Hell
getUser(1, (user) => {
  getOrders(user, (orders) => {
    getPayment(orders, (payment) => {
      console.log(payment);
    });
  });
});

How do you make an object immutable?

You can make an object immutable using Object.freeze(). It prevents adding, removing, or modifying an object's properties. However, Object.freeze() is shallow, meaning nested objects can still be modified unless they are also frozen.

const user = Object.freeze({
  name: 'John',
  age: 25
});

user.age = 30; // Ignored (or throws in strict mode)

console.log(user.age); // 25

When would you use a Set or a Map?

  • Set — a collection of unique values; great for de-duping ([...new Set(arr)]) and fast membership checks.
  • Mapkey/value pairs where keys can be any type (objects included), preserving insertion order and exposing .size.

Prefer Map over a plain object when keys are dynamic or non-string.

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

const map = new Map();
map.set('name', 'John');
map.set(1, 'One');
map.set({ id: 1 }, 'Object Key');

What do ?. and ?? do?

Optional chaining ?. short-circuits to undefined instead of throwing when accessing a property on null/undefined.

Nullish coalescing ?? returns the right side only when the left is null/undefined (unlike ||, it keeps 0, '', and false).

const city = user?.address?.city ?? 'Unknown';

Function declaration vs expression vs arrow function.

  • Function Declaration — fully hoisted, can be called before its definition, has its own this and arguments.
  • Function Expression — assigned to a variable, not callable before initialization, commonly used for callbacks and closures.
  • Arrow Function — concise syntax, lexically binds this, has no own arguments, prototype, or new. Best suited for callbacks and functional programming.
function decl() {}         // hoisted
const expr = function () {};// not hoisted
const arrow = () => {};     // lexical this

What is an IIFE and the module pattern?

An IIFE (Immediately Invoked Function Expression) is a function that runs immediately after it is created. It creates its own private scope, so variables inside it cannot be accessed from outside. The Module Pattern uses an IIFE to keep data private while exposing only the functions that should be accessible.

const counter = (function () {
  let count = 0; // Private variable

  return {
    increment() {
      count++;
    },
    getCount() {
      return count;
    }
  };
})();

counter.increment();
console.log(counter.getCount()); // 1

Explain destructuring with defaults and renaming.

Unpack arrays/objects into variables in one statement, with defaults for missing values and renaming for objects. Also enables clean swaps and nested extraction.

const { name = 'Guest', id: userId } = user;
const [first, , third] = list;
[a, b] = [b, a]; // swap

Object.keys(), Object.values(), Object.entries() and Object.fromEntries().

Four helpers for turning objects into arrays and back:

  • Object.keys(obj) — an array of the keys.
  • Object.values(obj) — an array of the values.
  • Object.entries(obj) — an array of [key, value] pairs.
  • Object.fromEntries(pairs) — rebuilds an object from those pairs.

Common combo: entriesmap/filterfromEntries to transform an object.

const user = {
  name: 'John',
  age: 25
};

console.log(Object.keys(user));
// ['name', 'age']

console.log(Object.values(user));
// ['John', 25]

console.log(Object.entries(user));
// [['name', 'John'], ['age', 25]]

const updated = Object.fromEntries(
  Object.entries(user).map(([key, value]) => [key, String(value)])
);

console.log(updated);
// { name: 'John', age: '25' }

JSON.stringify / parse — gotchas?

JSON.stringify() converts a JavaScript object into a JSON string, while JSON.parse() converts a JSON string back into a JavaScript object.

Common gotchas: undefined, functions, and symbols are omitted; Date objects become ISO strings; NaN and Infinity become null; BigInt throws an error; circular references cause JSON.stringify() to fail. Use a replacer and reviver to customize serialization and deserialization.

const obj = {
  name: 'John',
  created: new Date(),
  score: NaN
};

const json = JSON.stringify(obj, null, 2);
console.log(json);

const parsed = JSON.parse(json, (key, value) => {
  if (key === 'created') return new Date(value);
  return value;
});

console.log(parsed);

How does fetch() work, and how do you cancel a request?

fetch() is used to make HTTP requests. It returns a Promise that resolves to a Response object. Unlike many developers expect, fetch() does not throw an error for HTTP status codes like 404 or 500; you must check response.ok yourself. To cancel an ongoing request, use an AbortController.

const controller = new AbortController();

fetch('https://api.example.com/users', {
  signal: controller.signal
})
  .then(response => {
    if (!response.ok) {
      throw new Error('Request failed');
    }
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.log(error));

// Cancel the request
controller.abort();

Promise.all() vs Promise.allSettled() vs Promise.race() vs Promise.any().

Four ways to run multiple promises together:

  • Promise.all — waits for all to succeed; rejects as soon as one fails (fail-fast).
  • Promise.allSettled — waits for all and returns each one's status (never short-circuits).
  • Promise.race — settles with the first to finish, success or failure.
  • Promise.any — resolves with the first success (ignores failures until all fail).
const p1 = Promise.resolve('A');
const p2 = Promise.reject('Failed');
const p3 = Promise.resolve('C');

Promise.all([p1, p3]);
Promise.allSettled([p1, p2, p3]);
Promise.race([p1, p2, p3]);
Promise.any([p2, p3]);

What are generators and iterators?

An iterator is an object that allows sequential access to a collection using the next() method. A generator is a special function declared with function* that automatically creates an iterator. It can pause execution using yield and resume later, making it ideal for lazy evaluation, large datasets, and infinite sequences.

// Generator Function
function* numbers() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = numbers();

console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

// Using for...of
for (const n of numbers()) {
  console.log(n);
}

What is a Symbol and where is it used?

A Symbol is a unique and immutable primitive value introduced in ES6. It is mainly used as a unique object property key so that it does not conflict with other property names. Even if two Symbols have the same description, they are always different.

const id = Symbol('id');

const user = {
  name: 'John',
  [id]: 101
};

console.log(user[id]); // 101

What are WeakMap and WeakSet? When should you use them?

WeakMap and WeakSet are similar to Map and Set, but they store objects only using weak references. If an object is no longer used anywhere else in the application, JavaScript can automatically remove it from a WeakMap or WeakSet during garbage collection, helping prevent memory leaks.

const cache = new WeakMap();

const user = {
  name: 'John'
};

cache.set(user, {
  lastLogin: 'Today'
});

console.log(cache.get(user));
// { lastLogin: 'Today' }

Why does a for-loop with var log the wrong value in setTimeout()?

var is function-scoped, so all setTimeout() callbacks share the same i variable. By the time they execute, the loop has finished and i has its final value. let is block-scoped, so each loop iteration gets its own copy of i, producing the expected output.

// Using var
for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000);
}
// Output: 3 3 3

// Using let
for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000);
}
// Output: 0 1 2

typeof vs instanceof.

  • typeof — checks the type of a primitive ('string', 'number', 'boolean', 'function'). Quirk: typeof null is 'object'.
  • instanceof — checks whether an object was built from a given constructor/class by walking the prototype chain (x instanceof Array).

For arrays specifically, prefer Array.isArray(x).

typeof 'Hello';          // 'string'
typeof 123;              // 'number'
typeof true;             // 'boolean'
typeof undefined;        // 'undefined'
typeof function() {};    // 'function'

typeof [];               // 'object'
Array.isArray([]);       // true
[] instanceof Array;     // true
new Date() instanceof Date; // true

How do you check for NaN correctly?

Use Number.isNaN() to check if a value is NaN. Avoid the global isNaN() because it converts (coerces) values before checking, which can produce unexpected results.

Number.isNaN(NaN);       // true
Number.isNaN('foo');     // false

isNaN('foo');            // true (coerces 'foo' to NaN)
isNaN('123');            // false

How do you update the DOM efficiently?

To update the DOM efficiently, reduce the number of DOM changes. Instead of updating elements one by one, batch your changes and apply them together. You can use a DocumentFragment, toggle CSS classes instead of repeatedly changing inline styles, and use requestAnimationFrame() for smooth visual updates.

const fragment = document.createDocumentFragment();

items.forEach(item => {
  const li = document.createElement('li');
  li.textContent = item;
  fragment.appendChild(li);
});

list.appendChild(fragment); // Updates the DOM once

What is a pure function and why prefer it?

A pure function always produces the same output for the same input and does not cause side effects. It does not modify external variables, mutate its arguments, perform I/O operations, or depend on external state. Pure functions are predictable, easy to test, reusable, cacheable (memoization), and form the foundation of functional programming, Redux, and NgRx reducers.

// ✅ Pure Function
const add = (a, b) => a + b;

console.log(add(2, 3)); // 5
console.log(add(2, 3)); // 5

// ❌ Impure Function
let total = 0;

function addToTotal(value) {
  total += value;
  return total;
}

console.log(addToTotal(5)); // 5
console.log(addToTotal(5)); // 10

What are the data types in JavaScript?

Primitive (immutable, copied by value): string, number, boolean, bigint, symbol, undefined, null.

Non-primitive (reference type): object — which includes arrays, functions, and dates.

typeof 'a';     // 'string'
typeof 10n;     // 'bigint'
typeof null;    // 'object' (historical bug)
typeof [];      // 'object'

How do you convert between strings and numbers?

Number → string: String(n), n.toString(), or a template literal `${n}`.

String → number: Number(s), parseInt(s) (integer), parseFloat(s) (decimal), or the unary +s.

Number('123');      // 123
parseInt('123.45');  // 123
parseFloat('123.45');// 123.45
+'123';              // 123
(123).toString();    // '123'

Synchronous vs asynchronous programming.

Synchronous programming executes code one statement at a time. Each operation must finish before the next one starts, so long-running tasks block the execution.

Asynchronous programming allows time-consuming operations like API calls, timers, and file I/O to run without blocking the main thread. Once completed, the result is handled later using callbacks, Promises, or async/await, keeping the application responsive.

// Synchronous
console.log('Start');
console.log('Processing...');
console.log('End');

// Asynchronous
console.log('Start');

setTimeout(() => {
  console.log('Timer Finished');
}, 2000);

console.log('End');

// Output:
// Start
// End
// Timer Finished

Is JavaScript single-threaded or multi-threaded?

JavaScript is single-threaded — one call stack, one thing at a time. It feels concurrent because of the event loop: async work is offloaded to the browser/Node APIs and their callbacks are queued back onto the stack when it's free. (Web Workers add real parallel threads, but they don't share the main thread.)

What is an event listener?

An event listener is a function that waits for a specific event (such as a click, key press, or mouse movement) to occur on an HTML element. When that event happens, the function is automatically executed. Event listeners are usually attached using addEventListener().

const btn = document.getElementById('save');

btn.addEventListener('click', () => {
  console.log('Button clicked');
});

slice() vs splice().

  • slice(start, end) — returns a new array (a copy of a section); does not change the original.
  • splice(start, count, ...items)mutates the original by adding/removing/replacing, and returns the removed items.

Memory hook: splice changes the array.

const arr = ['a', 'b', 'c', 'd'];

const copied = arr.slice(1, 3);
console.log(copied); // ['b', 'c']
console.log(arr);    // ['a', 'b', 'c', 'd']

const removed = arr.splice(1, 2);
console.log(removed); // ['b', 'c']
console.log(arr);     // ['a', 'd']

call() vs apply() vs bind().

All three set this explicitly, differing in how/when they invoke:

  • call(thisArg, a, b) — invoke now, arguments listed.
  • apply(thisArg, [a, b]) — invoke now, arguments as an array.
  • bind(thisArg) — returns a new function with this fixed, to call later.
greet.call(obj, 'hi', '!');
greet.apply(obj, ['hi', '!']);
const bound = greet.bind(obj); bound('hi', '!');

What are template literals?

Template literals are strings enclosed in backticks (`) instead of quotes. They allow you to embed variables and expressions using ${...} and create multi-line strings without using string concatenation.

const name = 'John';
const age = 25;

const message = `Hello ${name}, you are ${age} years old.`;
console.log(message);

const multiLine = `Line 1
Line 2
Line 3`;

How does garbage collection work in JavaScript?

JavaScript uses automatic garbage collection (GC) to reclaim memory occupied by objects that are no longer reachable. Modern JavaScript engines (such as V8) primarily use the Mark-and-Sweep algorithm. Developers cannot manually free memory, but they can prevent memory leaks by removing unnecessary references, clearing timers, and unregistering event listeners.

// Object becomes unreachable
let user = {
  name: 'John'
};

user = null; // Previous object is now eligible for garbage collection

// Prevent memory leaks
const timer = setInterval(() => {
  console.log('Running...');
}, 1000);

clearInterval(timer);

How do you add or access properties dynamically on an object?

Use bracket notation when the key is dynamic or not a valid identifier: obj[key] = value. Dot notation (obj.key) only works for fixed, valid names.

const student = { name: 'A' };
const field = 'rollNumber';
student[field] = 42;   // { name: 'A', rollNumber: 42 }

for...of vs for...in.

  • for...of — loops over the values of an iterable (array, string, Map, Set).
  • for...in — loops over the keys (property names) of an object, including inherited ones.

Use for...of for arrays and for...in for plain objects — never for...in on an array.

const fruits = ['Apple', 'Banana', 'Orange'];

// for...of (values)
for (const fruit of fruits) {
  console.log(fruit);
}
// Apple
// Banana
// Orange

const user = {
  name: 'John',
  age: 25
};

// for...in (keys)
for (const key in user) {
  console.log(key, user[key]);
}
// name John
// age 25

setTimeout() vs setInterval().

  • setTimeout(fn, ms) — runs the function once after a delay; cancel with clearTimeout(id).
  • setInterval(fn, ms) — runs the function repeatedly every interval until you clearInterval(id).

The delay is a minimum — the callback waits for a free spot on the event loop.

// Runs once after 2 seconds
const timeoutId = setTimeout(() => {
  console.log('Executed once');
}, 2000);

// Cancel if needed
clearTimeout(timeoutId);

// Runs every 2 seconds
const intervalId = setInterval(() => {
  console.log('Runs repeatedly');
}, 2000);

// Stop the interval
clearInterval(intervalId);

What is variable shadowing?

When a variable declared in an inner scope has the same name as one in an outer scope, the inner one shadows (hides) the outer within that block. Common with function parameters and block-scoped let/const.

let x = 1;
function f() { let x = 2; return x; } // inner x shadows outer
f(); // 2, outer x still 1

Generator function vs normal function.

  • Normal function — runs start to finish and returns a single value.
  • Generator (function*) — can pause at each yield and resume on next(), keeping its state between calls.

A generator returns an iterator that produces values one at a time — ideal for lazy or infinite sequences.

function* numbers() {
  yield 1;
  yield 2;
  yield 3;
}

const iterator = numbers();

console.log(iterator.next());
// { value: 1, done: false }

console.log(iterator.next());
// { value: 2, done: false }

console.log(iterator.next());
// { value: 3, done: false }

console.log(iterator.next());
// { value: undefined, done: true }

Mutable vs immutable in JavaScript.

Mutable values can be changed after they are created, while immutable values cannot. In JavaScript, primitive values (string, number, boolean, null, undefined, symbol, bigint) are immutable, whereas objects and arrays are mutable.

// Immutable (String)
let name = 'John';
let upper = name.toUpperCase();

console.log(name);  // 'John'
console.log(upper); // 'JOHN'

// Mutable (Array)
const numbers = [1, 2];
numbers.push(3);

console.log(numbers); // [1, 2, 3]

// Mutable (Object)
const user = { name: 'John' };
user.age = 25;

console.log(user); // { name: 'John', age: 25 }

What are truthy and falsy values?

In a boolean context (if, &&, !), every value coerces to true or false. There are exactly 8 falsy values: false, 0, -0, 0n, "", null, undefined, and NaN. Everything else is truthy — including "0", "false", [], and {}.

if ([]) console.log('runs');   // [] is truthy!
Boolean('0');  // true
Boolean(0);    // false
Boolean(NaN);  // false

What does "use strict" do?

'use strict' enables Strict Mode, which makes JavaScript more secure by preventing common mistakes. For example, it throws an error if you use an undeclared variable, disallows duplicate parameter names, and changes some unsafe JavaScript behaviors into errors.

'use strict';

x = 10; // ❌ ReferenceError: x is not defined

let y = 20; // ✅ Correct
console.log(y);

find, filter, some, every, includes — what do they do?

  • filter — returns a new array of all items passing a test.
  • find — returns the first matching item (or undefined).
  • findIndex — the index of the first match (or -1).
  • sometrue if at least one item passes.
  • everytrue if all items pass.
  • includestrue if the array contains a value (uses ===).
const nums = [1, 2, 3, 4];
nums.filter(n => n % 2 === 0); // [2, 4]
nums.find(n => n > 2);        // 3
nums.some(n => n > 3);        // true
nums.every(n => n > 0);       // true
nums.includes(2);             // true

What is an Execution Context in JavaScript?

An Execution Context is the environment in which JavaScript code runs. It contains information about variables, functions, the value of this, and how the code should execute. Every JavaScript program starts with a Global Execution Context, and every function call creates a new Function Execution Context.

let message = 'Hello';

function greet(name) {
  const text = message + ' ' + name;
  console.log(text);
}

greet('John');

What is Lexical Scope?

Lexical Scope means JavaScript determines where variables are accessible based on where functions and variables are written in the code, not where they are called. An inner function can access variables from its own scope as well as all outer scopes. This behavior is known as the scope chain and is the foundation of closures.

const name = 'John';

function outer() {
  const city = 'Bangalore';

  function inner() {
    console.log(name);
    console.log(city);
  }

  inner();
}

outer();

What is the difference between localStorage, sessionStorage, and Cookies?

Three client-side storage options:

  • localStorage — persists until cleared; per-origin; ~5MB; not sent to the server.
  • sessionStorage — same API, but cleared when the tab closes.
  • Cookies — tiny (~4KB), and automatically sent with every HTTP request; used for auth/session (set HttpOnly + Secure).
// localStorage
localStorage.setItem('theme', 'dark');
console.log(localStorage.getItem('theme'));

// sessionStorage
sessionStorage.setItem('user', 'John');
console.log(sessionStorage.getItem('user'));

// Cookie
document.cookie = 'language=en; max-age=3600';

__proto__ vs prototype — what's the difference?

prototype — a property that exists only on functions: the object that will become the prototype of instances created with new. Methods you want shared go here.

__proto__ — the actual internal link ([[Prototype]]) that every object has, pointing at the object it inherits from. Lookup walks this chain.

They meet at construction: new Foo() sets the instance's __proto__ to Foo.prototype.

function Dog(name) { this.name = name; }
Dog.prototype.bark = function () { return this.name + ' woofs'; };

const d = new Dog('Rex');
Object.getPrototypeOf(d) === Dog.prototype;  // true — the link
d.bark();  // found via the chain: d -> Dog.prototype -> Object.prototype

What do Array flat() and flatMap() do?

flat(depth = 1) — flattens nested arrays by the given depth (Infinity for fully flat). flatMap(fn)map then flat(1) in one pass: each element maps to an array, results are concatenated.

flatMap doubles as a map-and-filter in one step: return [] to drop an element, [x] to keep one, [x, y] to expand.

[1, [2, [3, [4]]]].flat(Infinity);          // [1, 2, 3, 4]

orders.flatMap(o => o.items);                // List<List<T>> -> List<T>
nums.flatMap(n => n % 2 ? [n * 10] : []);    // filter + map in one pass

What are reflow and repaint?

Reflow (layout) — the browser recomputes geometry: positions and sizes of elements. Triggered by DOM structure changes, size/position CSS, font changes, window resize. Expensive — it can cascade through descendants and ancestors.

Repaint — redrawing pixels without geometry change (color, background, visibility). Cheaper than reflow; every reflow implies a repaint, not vice versa.

Layout thrashing — the killer pattern: interleaving writes with reads of layout properties (offsetHeight, getBoundingClientRect) forces a synchronous reflow per iteration.

// thrashing: read-write-read-write forces layout each loop
items.forEach(el => { el.style.width = el.offsetWidth + 10 + 'px'; });

// batched: read all, then write all
const widths = items.map(el => el.offsetWidth);
items.forEach((el, i) => { el.style.width = widths[i] + 10 + 'px'; });

ESM import/export vs CommonJS require — what's the difference?

  • CommonJS (require/module.exports) — Node's original system. Synchronous, dynamic (require anywhere, even conditionally), exports are a copied value at call time.
  • ESM (import/export) — the language standard. Static: imports/exports are declared at top level and analyzable without running the code — which is what enables tree-shaking, live read-only bindings, and async loading (import() for code splitting).

Browsers and modern bundlers are ESM-native; Node supports both ("type": "module" or .mjs). Angular/TS code is written as ESM and compiled.

// CommonJS — dynamic, sync
const { readFile } = require('fs');
if (dev) { const mock = require('./mock'); }   // legal

// ESM — static graph + async dynamic import for splitting
import { map } from 'rxjs';
const { Chart } = await import('chart.js');    // lazy chunk

Polyfill vs transpilation (Babel)?

Two different compatibility problems:

  • Transpilation — rewriting new syntax into old syntax at build time (Babel/TypeScript/esbuild): arrow functions → functions, optional chaining → ternary chains. Syntax can always be rewritten.
  • Polyfill — runtime code that implements missing APIs (Array.prototype.flat, fetch, Promise) by patching globals (core-js). APIs can't be transpiled away — the function either exists in the browser or must be provided.

A build targets browsers via browserslist; the toolchain decides which syntax to lower and which polyfills to include.

// you write:
const name = user?.profile?.name ?? 'guest';
// transpiled for old targets:
var name = (user && user.profile && user.profile.name) != null ? ... : 'guest';

// polyfill (conceptually):
if (!Array.prototype.flat) { Array.prototype.flat = function (d) { ... }; }

Implement a memoize function.

Memoization caches a pure function's results by argument, so repeat calls return instantly. The classic implementation is a closure over a Map keyed by the serialized arguments.

Points to mention while writing it: only valid for pure functions; the key strategy (JSON.stringify breaks on object key order and non-serializable args — a nested Map or WeakMap per object arg is the robust version); unbounded cache growth (real libraries add LRU eviction).

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 => { for (let i = 0; i < 1e7; i++); return n * n; };
const fast = memoize(slowSquare);
fast(9); // computed
fast(9); // cached — instant

ES6+ features rundown — which do you actually use?

The working set worth naming with a use for each:

  • let/const — block scope; arrow functions — lexical this;
  • Template literals, destructuring, spread/rest, default params — everyday ergonomics;
  • Promises → async/await (ES2017) — async flow;
  • Modules (import/export), classes;
  • Map/Set, Symbol, generators;
  • Later highlights: optional chaining ?. + nullish ?? (ES2020), Promise.allSettled, flat/flatMap (ES2019), Array.at(-1), top-level await, structuredClone, #private fields (ES2022).
const { name = 'guest', ...rest } = user ?? {};
const tags = [...new Set(items.flatMap(i => i.tags))];
const last = tags.at(-1);
await Promise.allSettled(jobs.map(j => run(j)));