Back to Categories
JavaScript / TypeScript

JavaScript / TypeScript

Closures, event loop, async/await, TypeScript generics and utility types

31Questions

Here’s a simple, muscle-memory friendly interview answer you can reuse directly.


🧠 Simple Definition (Word-for-word ready)

var, let, and const are ways to declare variables in JavaScript. The main differences are scope, re-declaration, and reassignment. var is function-scoped and can be re-declared. let is block-scoped and can be reassigned but not re-declared in the same scope. const is also block-scoped but cannot be reassigned.


⚡ Super Simple Summary Line

var = function scope + can re-declare let = block scope + can reassign const = block scope + cannot reassign


🧪 Simple Example

var a = 10;
var a = 20; // allowed

let b = 10;
b = 20; // allowed

const c = 10;
c = 20; // ❌ error

📦 Scope Example (VERY IMPORTANT)

if (true) {
  var x = 1;
  let y = 2;
  const z = 3;
}

console.log(x); // ✅ 1
console.log(y); // ❌ not accessible
console.log(z); // ❌ not accessible

🧠 One-line Interview Explanation

var ignores block scope, while let and const respect block scope. let allows reassignment, const does not allow reassignment.


🚀 10-Second Version (if interviewer rushes you)

var is function-scoped and flexible, let is block-scoped and reassignable, and const is block-scoped and fixed after assignment.

🧠 Simple Definition (Word-for-word)

🧠 Event Loop (Simple Definition) The event loop is a mechanism that decides when asynchronous code (like promises and timers) should run by checking if the main thread (call stack) is free.


⚡ Super Simple Line

📦 Call Stack (simple memory line) The call stack is where JavaScript runs code line by line.


⚡ Key Details & Explanation

🧠 Event Loop (Simple Definition)

The event loop is a mechanism that decides when asynchronous code (like promises and timers) should run by checking if the main thread (call stack) is free.

📦 Call Stack (simple memory line)

The call stack is where JavaScript runs code line by line.

📬 Microtask Queue

Microtasks are high-priority tasks (like Promises) that run right after sync code finishes.

🟡 Macrotask Queue

Macrotasks are normal delayed tasks (like setTimeout) that run after microtasks.

⚡ One-Line Interview Answer (MEMORIZE THIS)

JavaScript executes synchronous code in the call stack. Asynchronous tasks go into queues. The event loop moves tasks back to the call stack when it is empty, always running microtasks before macrotasks.

🧪 Simple Example console.log("A");

setTimeout(() => { console.log("B"); }, 0);

Promise.resolve().then(() => { console.log("C"); });

console.log("D"); ✅ Output A D C B 🧠 How to Explain in Interview (Short Talk)

You can say:

“First, JavaScript runs all synchronous code in the call stack. So A and D print first. Then async tasks are handled: Promises go to microtask queue, setTimeout goes to macrotask queue. The event loop first clears all microtasks, so C runs before B.”

🚀 Super Memory Trick

Think in this order:

Sync → Microtask (Promise) → Macrotask (setTimeout)


⚡ One-line Interview Answer

🧠 Event Loop (Simple Definition) The event loop is a mechanism that decides when asynchronous code (like promises and timers) should run by checking if the main thread (call stack) is free.

🧠 Simple Definition (Word-for-word)

Here’s a simple, interview-ready answer you can memorize easily.


⚡ Super Simple Line

🧠 Simple Definition (Word-for-word) A closure is when a function remembers variables from its outer scope even after that outer function has finished executing.


⚡ Key Details & Explanation

Here’s a simple, interview-ready answer you can memorize easily.

🧠 Simple Definition (Word-for-word)

A closure is when a function remembers variables from its outer scope even after that outer function has finished executing.

⚡ Super Simple Line

Closure = a function + its remembered variables from where it was created.

🧪 Simple Example function outer() { let count = 0;

return function inner() { count++; console.log(count); }; }

const counter = outer();

counter(); // 1 counter(); // 2 counter(); // 3 🧠 What is happening? outer() finishes execution but inner() still remembers count because of closure 🌍 Real-World Use Case (Very Important) 👉 Data Privacy / Encapsulation

Example: private counter

function createBankAccount() { let balance = 0;

return { deposit(amount) { balance += amount; console.log(balance); }, getBalance() { console.log(balance); } }; }

const account = createBankAccount();

account.deposit(100); // 100 account.deposit(50); // 150 account.getBalance(); // 150 🧠 Why this is closure? balance is NOT accessible directly but methods still access it because they remember outer scope ⚡ One-line Interview Answer

Closure is when a function retains access to variables from its outer scope even after the outer function has finished executing. It is commonly used for data privacy and state management.

🚀 10-Second Version (if interviewer rushes you)

Closure means a function remembers its outer variables even after the outer function is done. It is useful for creating private variables like counters or encapsulated state.


⚡ One-line Interview Answer

Here’s a simple, interview-ready answer you can memorize easily.

🧠 Simple Definition (Word-for-word)

Prototypal inheritance is a mechanism in JavaScript where objects inherit properties and methods directly from other objects through a prototype chain. Classical inheritance is an OOP model where classes define templates/blueprints, and objects are created as instances of these classes, copying or referencing properties based on class hierarchy.


⚡ Super Simple Line

Prototypal = objects inherit directly from objects (linked dynamic prototypes).
Classical = objects are instantiated from static class blueprints.


🧪 Code Example

// 1. Prototypal Inheritance (Pure Object-to-Object)
const parent = {
  greet() { return `Hello from ${this.name}`; }
};
const child = Object.create(parent);
child.name = "Child Object";
console.log(child.greet()); // ✅ "Hello from Child Object" (found via prototype chain)

// 2. Classical Style (ES6 syntax syntactic sugar over prototype)
class Person {
  constructor(name) { this.name = name; }
  greet() { return `Hello from ${this.name}`; }
}
const instance = new Person("Class Instance");
console.log(instance.greet());

📊 Key Differences Table

FeaturePrototypal InheritanceClassical Inheritance
Source of InheritanceDirectly from other objects (Prototypes)From Class blueprints / schemas
Under the HoodPrototype chain (dynamic pointers)Class hierarchies (structural cloning/lookup)
FlexibilityObjects can be modified at runtime directlyClasses are static and defined compile-time
Memory EfficiencyHighly efficient (methods shared on prototype)Depends on language, usually clones fields

⚡ One-line Interview Answer

JavaScript uses prototypal inheritance, which links objects directly to other objects via a prototype chain, whereas classical inheritance uses class blueprints to instantiate objects, lacking JavaScript's dynamic object-to-object links.

🧠 Simple Definition (Word-for-word)

Here’s a simple interview-ready answer you can memorize easily.


⚡ Super Simple Line

🧠 Simple Definition (Word-for-word) In regular functions, this depends on how the function is called.


⚡ Key Details & Explanation

Here’s a simple interview-ready answer you can memorize easily.

🧠 Simple Definition (Word-for-word)

In regular functions, this depends on how the function is called. In arrow functions, this is not their own — it is taken from the surrounding (lexical) scope.

⚡ Super Simple Line

Regular function → this is dynamic (depends on caller) Arrow function → this is fixed (inherits from outside)

🧪 Simple Example (Regular Function) const user = { name: "A", show: function () { console.log(this.name); } };

user.show(); // A

👉 this = user because user is calling it

🧪 Example (Arrow Function) const user = { name: "A", show: () => { console.log(this.name); } };

user.show(); // undefined

👉 Arrow function does NOT have its own this 👉 It takes this from outside scope

🧠 Key Difference Explained Simply 🟢 Regular Function this depends on who calls it can change at runtime 🔵 Arrow Function this depends on where it is written cannot change 🌍 Real Use Case (Important) 👉 React / callbacks problem const obj = { name: "A", show: function () { setTimeout(function () { console.log(this.name); }, 1000); } };

obj.show();

👉 Output: undefined

Fix using arrow function: const obj = { name: "A", show: function () { setTimeout(() => { console.log(this.name); }, 1000); } };

obj.show();

👉 Output: A

🧠 Why? Arrow function inside setTimeout inherits this from show So this = obj ⚡ One-line Interview Answer

In regular functions, this is determined by how the function is called, while in arrow functions, this is lexically inherited from the surrounding scope and cannot be changed.

🚀 10-Second Version

Regular functions have dynamic this based on the caller, but arrow functions inherit this from their surrounding scope and do not have their own this.


⚡ One-line Interview Answer

Here’s a simple interview-ready answer you can memorize easily.

🧠 Simple Definition (Word-for-word)

Loose equality (==) compares two values for equality after converting them to a common type (performing type coercion). Strict equality (===) compares both the value and the type directly, without coercion; it returns true only if both the value and type are identical.


⚡ Super Simple Line

== performs type coercion before comparing.
=== compares value and type directly (no coercion).


🧪 Simple Example

5 == "5"        // ✅ true (coercion converts "5" to number 5)
5 === "5"       // ❌ false (different types: number vs string)

0 == false      // ✅ true (coercion converts false to 0)
0 === false     // ❌ false (different types: number vs boolean)

🔥 Coercion Edge Cases (Interview Gotchas)

[] == ![]       // ✅ true (evaluates to 0 == 0 after coercion)
"" == 0         // ✅ true (empty string coerces to 0)
null == undefined // ✅ true (special rule in specification)
null === undefined // ❌ false

📊 Comparison Table

ExpressionLoose Equality (==)Strict Equality (===)
5 == "5"truefalse
null == undefinedtruefalse
0 == falsetruefalse
[] == ![]truefalse

⚡ One-line Interview Answer

Loose equality (==) automatically coerces types before comparing, which can lead to unpredictable behaviors, while strict equality (===) compares both value and type without coercion and is the recommended standard for production code.


🚀 10-Second Version

Use === because it checks both value and type. Avoid == because its automatic type conversion leads to silent bugs.

🧠 Simple Definition (Word-for-word)

Here’s a simple, interview-ready answer you can memorize easily.


⚡ Super Simple Line

🧠 Simple Definition (Word-for-word) Promise.all, Promise.allSettled, Promise.race, and Promise.any are methods used to handle multiple promises at the same time, but they behave differently based on how they resolve or reject.


⚡ Key Details & Explanation

Here’s a simple, interview-ready answer you can memorize easily.

🧠 Simple Definition (Word-for-word)

Promise.all, Promise.allSettled, Promise.race, and Promise.any are methods used to handle multiple promises at the same time, but they behave differently based on how they resolve or reject.

⚡ Super Simple Summary Line

all = wait for everything allSettled = wait for everything (no matter success/fail) race = first one wins (success or fail) any = first success wins (ignore failures)

🧪 Simple Example Setup const p1 = new Promise(res => setTimeout(() => res("A"), 1000)); const p2 = new Promise(res => setTimeout(() => res("B"), 2000)); const p3 = new Promise((_, rej) => setTimeout(() => rej("C failed"), 500)); 🔵 1. Promise.all Promise.all([p1, p2, p3]) .then(console.log) .catch(console.log); ❗ Output: C failed 🧠 Why: Fails immediately if any one fails If all succeed → returns all results 🟡 2. Promise.allSettled Promise.allSettled([p1, p2, p3]) .then(console.log); ✅ Output: [ { status: "fulfilled", value: "A" }, { status: "fulfilled", value: "B" }, { status: "rejected", reason: "C failed" } ] 🧠 Why: Waits for ALL promises Never fails early 🔴 3. Promise.race Promise.race([p1, p2, p3]) .then(console.log) .catch(console.log); ❗ Output: C failed 🧠 Why: First promise to finish wins It can be success OR failure 🟢 4. Promise.any Promise.any([p1, p2, p3]) .then(console.log) .catch(console.log); ✅ Output: A 🧠 Why: First SUCCESS wins Ignores failures Fails only if ALL fail 🧠 Key Difference Table Method Waits for Fails when Returns all all promises any fails all results allSettled all promises never fails early status of all race first to finish first result (success/fail) single value any first success all fail first success 🌍 Real-World Use Cases 🔵 Promise.all

👉 Load page data (must succeed all APIs)

🟡 Promise.allSettled

👉 Dashboard reporting (show both success & failed APIs)

🔴 Promise.race

👉 Timeout handling / fastest server response

🟢 Promise.any

👉 Try multiple servers, take first working one

⚡ One-line Interview Answer

Promise.all waits for all promises and fails if any fail, allSettled waits for all regardless of result, race returns the first completed promise whether success or failure, and any returns the first successful promise ignoring failures.

🚀 10-Second Version

all = all must succeed allSettled = wait for all results race = first finished wins any = first success wins


⚡ One-line Interview Answer

Here’s a simple, interview-ready answer you can memorize easily.

Here’s a simple, interview-ready answer you can memorize easily.

🧠 Simple Definition (Word-for-word)

async/await is a cleaner way to work with Promises. Under the hood, async functions return a Promise, and await pauses execution until the Promise is resolved, without blocking the main thread.


⚡ Super Simple Line

async/await = Promises made readable, where await pauses function execution and resumes it later.


🧪 Simple Example

async function test() {
  console.log("A");

  const result = await Promise.resolve("B");

  console.log(result);
}

test();
console.log("C");

✅ Output

A
C
B

🧠 What is happening?

Step-by-step:

  1. test() starts → runs sync code → prints A
  2. await is hit → function PAUSES
  3. Rest of JS continues → prints C
  4. Promise resolves → function resumes → prints B

⚙️ Under the hood (IMPORTANT)

async/await is just syntactic sugar over Promises and microtasks.

When you write:

async function f() {
  await something;
}

JS internally converts it to something like:

function f() {
  return Promise.resolve(something)
    .then(() => {
      // rest of code
    });
}

📦 Key Idea

🟢 async function

  • Always returns a Promise

🟡 await

  • Pauses execution of THAT function
  • Does NOT block main thread
  • Rest resumes in microtask queue

🧠 Event Loop Connection

When await happens:

  • Function splits into 2 parts:

    1. Before await → runs immediately
    2. After await → goes to microtask queue

🌍 Real Mental Model

Think of it like:

async function = paused movie await = pause button microtask queue = resume queue



📈 How it Improves Readability

  1. Avoids Promise Nesting ("Callback Hell"): Allows writing async logic sequentially from top to bottom, avoiding deep nesting of .then() callbacks.
  2. Native Error Handling: Standard synchronous try/catch blocks can handle both synchronous runtime errors and asynchronous network errors in one unified place.
  3. Intuitive Flow: Conditional branches and loops (e.g. for...of loops with await) work exactly like synchronous code, rather than requiring complex recursion or helper libraries.
  4. Better Debugging: Setting breakpoints on sequential lines works naturally, and error stack traces accurately point to the exact line where execution failed.

⚡ One-line Interview Answer

async/await is syntactic sugar over Promises where async functions return a Promise and await pauses execution of that function until the Promise resolves, resuming it later in the microtask queue without blocking the main thread.


🚀 10-Second Version

async/await makes Promises easier to read. async returns a Promise, and await pauses the function until the Promise resolves, then continues execution asynchronously.

Here’s a simple, interview-ready answer you can memorize easily.


🧠 Simple Definition (Word-for-word)

WeakMap and WeakSet are special data structures in JavaScript that store objects weakly, meaning if there are no other references to those objects, they can be automatically garbage collected.


⚡ Super Simple Line

WeakMap/WeakSet = collections that don’t prevent garbage collection of objects.


🧪 WeakMap Example

let obj = { name: "A" };

const map = new WeakMap();
map.set(obj, "user data");

obj = null; 

🧠 What happens?

  • Object { name: "A" } has no references anymore
  • Garbage collector removes it automatically
  • WeakMap entry also disappears

📦 WeakMap Rules

  • Keys must be objects only
  • Not iterable (you cannot loop it)
  • No .size

🧪 WeakSet Example

let user = { name: "A" };

const set = new WeakSet();
set.add(user);

user = null;

🧠 What happens?

  • Object is removed from memory
  • WeakSet automatically forgets it

📦 WeakSet Rules

  • Only stores objects
  • Cannot store primitives (no numbers, strings)
  • Not iterable

🔥 Key Difference from Map/Set

Feature Map/Set WeakMap/WeakSet
Key type Any value Only objects
Garbage collection No Yes (automatic)
Iterable Yes No
Size property Yes No

🌍 Real-World Use Case

👉 Memory-safe caching / metadata

const cache = new WeakMap();

function processUser(user) {
  cache.set(user, { processed: true });
}

Why useful?

  • If user object is deleted elsewhere
  • cache entry is automatically cleaned

👉 DOM element tracking

const visited = new WeakSet();

function markVisited(element) {
  visited.add(element);
}

👉 If DOM element is removed → memory is freed automatically


🧠 Why WeakMap/WeakSet exist?

To avoid memory leaks when objects are no longer needed.


⚡ One-line Interview Answer

WeakMap and WeakSet are collections that store objects weakly, meaning they do not prevent garbage collection. If an object has no other references, it is automatically removed from memory along with its entry.


🚀 10-Second Version

WeakMap and WeakSet store objects without preventing garbage collection. When the object is no longer referenced, it is automatically cleaned up.

Here’s a simple, interview-ready answer you can memorize easily.


🧠 Simple Definition

Debouncing and throttling are techniques used to control how often a function is executed when an event happens repeatedly.


⚡ Super Simple Line

  • Debounce → run after user stops triggering
  • Throttle → run at fixed intervals while user is triggering

🧪 Debouncing Example (Search Input)

👉 Only run after user stops typing

function debounce(fn, delay) {
  let timer;

  return function (...args) {
    clearTimeout(timer);

    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

🧪 Usage Example

const search = debounce((text) => {
  console.log("Searching:", text);
}, 500);

search("r");
search("re");
search("rea");
search("react");

👉 Only runs once after user stops typing for 500ms


🧠 How Debounce Works

  • Every call resets the timer
  • Only the LAST call after pause executes

🌍 Real Use Case

  • Search box autocomplete
  • Form validation
  • Window resize optimization

⚡ Throttling (Quick Understanding)

Throttling ensures a function runs at most once every fixed time interval.


🧪 Throttle Example

function throttle(fn, limit) {
  let lastCall = 0;

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

    if (now - lastCall >= limit) {
      lastCall = now;
      fn.apply(this, args);
    }
  };
}

🧠 Difference Summary

Feature Debounce Throttle
When it runs After user stops At intervals
Execution One final call Multiple controlled calls
Use case Search input Scroll, resize

🌍 Real Example

  • Debounce → “Wait until user stops typing”
  • Throttle → “Run every 200ms while scrolling”

⚡ One-line Interview Answer

Debouncing delays function execution until the user stops triggering the event, while throttling ensures the function executes at fixed intervals during continuous triggering.


🚀 10-Second Version

Debounce runs after activity stops, throttling runs periodically during activity.


🧠 Bonus (Interview gold line)

Debounce = “wait for silence” Throttle = “run at intervals”

🧠 Simple Definition (Word-for-word)

Here’s a simple, interview-ready answer you can memorize easily.


⚡ Super Simple Line

🧠 Simple Definition (Word-for-word) In TypeScript, both interface and type are used to define the shape of data.


⚡ Key Details & Explanation

Here’s a simple, interview-ready answer you can memorize easily.

🧠 Simple Definition (Word-for-word)

In TypeScript, both interface and type are used to define the shape of data. The main difference is that interface is best for object structure and can be extended, while type is more flexible and can represent unions, primitives, and more complex types.

⚡ Super Simple Line

interface = object shape (extendable) type = flexible (can represent anything)

🧪 Simple Example (Interface) interface User { name: string; age: number; }

const user: User = { name: "A", age: 20 }; 🧪 Simple Example (Type) type User = { name: string; age: number; };

const user: User = { name: "A", age: 20 }; 🔥 Key Differences

  1. Extension Interface interface A { name: string; }

interface A { age: number; }

👉 Automatically merges (declaration merging)

Type type A = { name: string; };

// ❌ Cannot redeclare same type 2. Flexibility Type can do more: type ID = string | number;

👉 Interfaces cannot do unions/primitives

  1. Extending Interface interface A { name: string; }

interface B extends A { age: number; } Type type A = { name: string; };

type B = A & { age: number; }; 🧠 When to use what? Use interface when: Defining object shapes Working with classes / OOP style You want extension & merging Use type when: Unions (string | number) Complex types Advanced composition ⚡ One-line Interview Answer

Interface is mainly used for defining object structures and supports extension and declaration merging, while type is more flexible and can represent primitives, unions, intersections, and complex types.

🚀 10-Second Version

Interface is best for object shapes and can be extended, while type is more flexible and can represent unions and complex types.


⚡ One-line Interview Answer

Here’s a simple, interview-ready answer you can memorize easily.

🧠 Simple Definition (Word-for-word)

Here’s a simple, interview-ready answer you can memorize easily.


⚡ Super Simple Line

🧠 Simple Definition (Word-for-word) Generics in TypeScript allow you to write reusable code where the type is not fixed in advance, but is decided when the function, class, or interface is used.


⚡ Key Details & Explanation

Here’s a simple, interview-ready answer you can memorize easily. 🧠 Simple Definition (Word-for-word) Generics in TypeScript allow you to write reusable code where the type is not fixed in advance, but is decided when the function, class, or interface is used. ⚡ Super Simple Line Generics = “type as a parameter” → write once, use with any type safely. 🧪 Simple Example (Function) function identity(value: T): T { return value; } identity("Hello"); identity(10); 🧠 What is happening? T is a placeholder for type Type is decided when function is called Ensures type safety + reusability 🧪 Another Example (Array) function getFirst(arr: T[]): T { return arr[0]; } getFirst([1, 2, 3]); getFirst(["a", "b"]); 🌍 Real-World Use Case 👉 API response handling type ApiResponse = { data: T; error: string | null; }; const userResponse: ApiResponse<{ name: string }> = { data: { name: "A" }, error: null }; 🧠 Why this is useful? Same structure works for different data types Avoids rewriting types again and again Keeps type safety 🧪 Generic in Interface interface Box { value: T; } const box1: Box = { value: "Hello" }; const box2: Box = { value: 100 }; 🧠 Key Idea Generics let you create reusable components that work with multiple types while still keeping strict type checking. ⚡ One-line Interview Answer Generics in TypeScript allow us to create reusable functions, interfaces, and classes that work with different data types while maintaining type safety by passing the type as a parameter. 🚀 10-Second Version Generics let you write reusable code where the type is passed dynamically, ensuring flexibility and type safety at the same time. 🧠 Super Memory Trick Generic = “Type variable (T) that gets replaced later”

⚡ One-line Interview Answer

Here’s a simple, interview-ready answer you can memorize easily.

🧠 Simple Definition (Word-for-word)

Utility types in TypeScript are built-in helpers that let you transform existing types into new ones in a reusable and type-safe way.


⚡ Super Simple Line

Utility types = “type transformers” (modify existing types easily)


⚡ Key Details & Explanation

We will use this base User type for all examples below:

type User = {
  id: number;
  name: string;
  age: number;
};

1. Partial<T>

Makes all properties optional.

type PartialUser = Partial<User>;

// Result:
// {
//   id?: number;
//   name?: string;
//   age?: number;
// }

👉 Use case: Updating only a subset of fields (e.g., in a PUT/PATCH request).

2. Pick<T, K>

Selects only specific properties from a type.

type NameOnly = Pick<User, "name" | "age">;

// Result:
// {
//   name: string;
//   age: number;
// }

3. Omit<T, K>

Removes specific properties from a type.

type WithoutAge = Omit<User, "age">;

// Result:
// {
//   id: number;
//   name: string;
// }

4. Record<K, T>

Creates an object type with specific keys and value type.

type Roles = "admin" | "user" | "guest";
type RoleAccess = Record<Roles, boolean>;

// Result:
// {
//   admin: boolean;
//   user: boolean;
//   guest: boolean;
// }

5. ReturnType<T>

Extracts the return type of a function type.

function getUser() {
  return { id: 1, name: "A" };
}
type UserReturn = ReturnType<typeof getUser>;

// Result:
// {
//   id: number;
//   name: string;
// }

📊 Quick Summary Table

Utility TypeWhat it does
Partial<T>makes all fields optional
Pick<T, K>selects specific fields
Omit<T, K>removes specific fields
Record<K, T>creates object from keys + value type
ReturnType<T>extracts function return type

🧠 Memory Trick

Remember “PPORR”:

  • Partial
  • Pick
  • Omit
  • Record
  • ReturnType

⚡ One-line Interview Answer

Utility types in TypeScript are built-in helpers that allow us to transform existing types. For example, Partial makes properties optional, Pick selects specific fields, Omit removes fields, Record creates key-value mappings, and ReturnType extracts a function’s return type.

🧠 Simple Definition (Word-for-word)

In TypeScript, any, unknown, and never are special types used to control how flexible or strict a value can be. any disables type checking, unknown is a safe version of any, and never represents values that never occur.


⚡ Super Simple Line

any = no type safety
unknown = safe any (must check before use)
never = something that never happens


🧪 1. any

let value: any = 10;

value = "hello";
value.toUpperCase(); // ❌ no type safety

🧠 Meaning:

  • You can do anything

  • TypeScript stops checking


🧪 2. unknown

let value: unknown = "hello";

// value.toUpperCase(); ❌ error

if (typeof value === "string") {
  value.toUpperCase(); // ✅ safe
}

🧠 Meaning:

  • You must check type before using

  • Safer than any


🧪 3. never

function error(message: string): never {
  throw new Error(message);
}

🧠 Meaning:

  • Function never returns

  • Used for errors or infinite loops


🔥 Another never example

function infiniteLoop(): never {
  while (true) {}
}

🧠 Key Differences

TypeMeaningSafetyanydisable type checking❌ unsafeunknownunknown value, must check before use✅ safenevernever produces a valuespecial


🌍 Real-World Use Cases

👉 any

  • Quick prototyping

  • Legacy JS migration

👉 unknown

  • API responses

  • External data (safer handling)

👉 never

  • Error functions

  • Exhaustive checks in switch cases


🧠 Important Insight

unknown forces you to verify the type, while any skips type checking completely, and never represents impossible values.


⚡ One-line Interview Answer

any disables type checking completely, unknown is a safer version of any that requires type checking before use, and never represents values that never occur, such as functions that throw errors or never return.


🚀 10-Second Version

any is unsafe and disables checking, unknown is safe and requires type checks, and never represents values that never exist or functions that never return.


🧠 Memory Trick

“A-U-N”

  • any = Allow everything

  • unknown = Use only after checking

  • never = No return

🧠 Simple Definition (Word-for-word)

A discriminated union in TypeScript is a pattern where multiple object types share a common literal property, which is used to safely distinguish between them.


⚡ Super Simple Line

Discriminated union = multiple types + a shared “type” field to identify them safely.


🧪 Simple Example

type Circle = {
  type: "circle";
  radius: number;
};

type Square = {
  type: "square";
  side: number;
};

type Shape = Circle | Square;

🧠 How it works

function getArea(shape: Shape) {
  if (shape.type === "circle") {
    return Math.PI * shape.radius * shape.radius;
  } else {
    return shape.side * shape.side;
  }
}

🧠 What is happening?

  • type is the discriminator
  • TypeScript uses it to narrow the type automatically
  • You get safe access to properties

🌍 Real-World Use Case

👉 API response handling

type Success = {
  status: "success";
  data: string;
};

type Error = {
  status: "error";
  message: string;
};

type Response = Success | Error;

function handle(res: Response) {
  if (res.status === "success") {
    console.log(res.data);
  } else {
    console.log(res.message);
  }
}

🧠 Why it is useful?

  • Prevents accessing wrong properties
  • Makes code safer
  • Helps TypeScript auto-narrow types

⚡ One-line Interview Answer

A discriminated union is a TypeScript pattern where multiple object types share a common literal property, which allows TypeScript to safely narrow the type based on that property.


🚀 10-Second Version

Discriminated union is a union of object types that share a common “type” field, used to identify and safely narrow which type is being used.


🧠 Memory Trick

“Same field → different meanings → TypeScript uses it to decide the type”

🧠 Simple Definition (Word-for-word)

In TypeScript, async functions always return a Promise, so we type them using Promise<T>. For API responses, we define a response type and use interfaces or generics to ensure the returned data is type-safe.


⚡ Super Simple Line

async function → always returns Promise
API typing → define response shape + wrap in Promise or generics


🧪 1. Typing an Async Function

async function getNumber(): Promise<number> {
  return 42;
}

🧠 What is happening?

  • async automatically wraps the return value in a Promise

  • So we explicitly type it as Promise<number>

Equivalent to:

function getNumber(): Promise<number> {
  return Promise.resolve(42);
}

🧪 Another Example

async function getUser(): Promise<{ name: string }> {
  return { name: "A" };
}

🌍 2. Typing API Responses (Basic Way)

type User = {
  id: number;
  name: string;
};

async function fetchUser(): Promise<User> {
  const res = await fetch("/api/user");
  return res.json();
}

🧠 Why this is good?

  • Ensures the API response matches the expected structure

  • Provides autocomplete and type checking

  • Helps prevent runtime errors such as undefined.name


🧪 3. Using Generics (Best Practice)

type ApiResponse<T> = {
  data: T;
  error: string | null;
};

async function fetchData<T>(
  url: string
): Promise<ApiResponse<T>> {
  const res = await fetch(url);
  const data = await res.json();

  return {
    data,
    error: null,
  };
}

🧠 Usage Example

type User = {
  name: string;
};

const result = await fetchData<User>("/api/user");

TypeScript now knows:

result.data.name;

is a string.


🌍 Why this is powerful?

  • Reusable for any API response type

  • Fully type-safe

  • Better autocomplete

  • Prevents incorrect assumptions about response data


⚡ One-line Interview Answer

Async functions in TypeScript always return a Promise, so we type them as Promise<T>. For API responses, we define interfaces, types, or generics to ensure the returned data is strongly typed and safe to use.


🚀 10-Second Version

Async functions return Promise<T>. We type API responses using interfaces, types, or generics so the returned data is safe, predictable, and type-checked.


🧠 Memory Trick

Async = Promise<T>
API = Typed Promise + Structure

Example:

async function getUser(): Promise<User> {
  ...
}

Think:

async
↓
returns Promise
↓
Promise contains User
↓
Promise<User>

🧠 Simple Definition (Word-for-word)

In JavaScript, the + operator tries to convert values into strings when at least one operand is not a number. This process is called type coercion, and it can lead to some surprising results with arrays and objects.


⚡ Super Simple Line

+ triggers type coercion
Arrays → strings
Objects → strings ([object Object])


🧪 1. [] + []

[] + []

✅ Output

""

🧠 What is happening?

Both arrays are converted to strings:

[].toString(); // ""
[].toString(); // ""

So JavaScript sees:

"" + ""

Result:

""

🧪 2. [] + {}

[] + {}

✅ Output

"[object Object]"

🧠 What is happening?

The array becomes an empty string:

[] → ""

The object becomes:

{} → "[object Object]"

So JavaScript evaluates:

"" + "[object Object]"

Result:

"[object Object]"

🧪 3. {} + []

{} + []

✅ Output

0

(in many browser consoles)


🧠 Why is this confusing?

JavaScript can interpret {} in two different ways.


Case 1: {} is treated as a block

{} + []

JavaScript sees:

{}
+[]

Meaning:

  • {} → empty code block

  • +[] → unary plus

Now:

Number([])

becomes:

0

Result:

0

Case 2: {} is treated as an object

({} + [])

Now JavaScript must treat {} as an object.

Object becomes:

"[object Object]"

Array becomes:

""

So:

"[object Object]" + ""

Result:

"[object Object]"

⚡ Final Interview Table

ExpressionOutput[] + []""[] + {}"[object Object]"{} + []0 (or "[object Object]" depending on context)({} + [])"[object Object]"


🧠 Why Does This Happen?

JavaScript follows these steps:

  1. Try to convert operands to primitive values

  2. If one side becomes a string, perform string concatenation

  3. Otherwise, perform numeric addition

  4. Special parsing rules can make {} behave as a block instead of an object


⚡ One-line Interview Answer

These results occur because JavaScript performs type coercion when using the + operator. Arrays and objects are converted to primitive values, and {} can sometimes be interpreted as a code block instead of an object depending on the parsing context.


🚀 10-Second Version

+ forces type conversion. Arrays become empty strings, objects become "[object Object]", and {} can behave differently because JavaScript may treat it as either a block or an object.


🧠 Memory Trick

[]      → ""
{}      → "[object Object]"
+       → forces conversion
{} + [] → special parsing rule

Think:

Array = ""
Object = "[object Object]"
+ = convert first, then combine

Here’s a simple, interview-ready answer you can memorize easily.


🧠 Simple Definition

Memoization is a technique where we cache the result of a function so that if the same input occurs again, we return the cached result instead of recomputing it.


⚡ Super Simple Line

memoize = “remember previous results and reuse them”


🧪 Basic Implementation

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(...args);
    cache.set(key, result);

    return result;
  };
}

🧠 How it works

  • Store inputs as a key

  • Store output as value

  • If same input comes again → return cached value


🧪 Example Usage

function add(a, b) {
  console.log("Computing...");
  return a + b;
}

const memoizedAdd = memoize(add);

console.log(memoizedAdd(2, 3)); // Computing... 5
console.log(memoizedAdd(2, 3)); // 5 (cached, no computing)

🌍 Why this is useful?

  • Improves performance

  • Avoids repeated expensive calculations

  • Common in React, algorithms, APIs


⚡ One-line Interview Answer

Memoization is an optimization technique where we cache function results based on input arguments so that repeated calls with the same inputs return the cached result instead of recomputing.


🚀 10-Second Version

Memoization stores function results for given inputs and returns cached results when the same inputs occur again.


🧠 Memory Trick

“Same input → skip work → return stored result”


Here’s a simple, interview-ready answer you can memorize easily.


🧠 Simple Definition

Promise.all takes multiple promises and returns a single promise that resolves when all promises succeed, or rejects immediately if any promise fails.


⚡ Super Simple Line

Promise.all = “wait for all promises, fail fast if one fails”


🧪 Example Usage

Promise.all([
  Promise.resolve(1),
  Promise.resolve(2),
  Promise.resolve(3)
]).then(console.log);

Output:

[1, 2, 3]

🧠 How it works

  1. Wait for all promises

  2. Store results in correct order

  3. Resolve when all are done

  4. Reject immediately if one fails


🧪 Implementation from Scratch

function myPromiseAll(promises) {
  return new Promise((resolve, reject) => {
    const results = [];
    let completed = 0;

    if (promises.length === 0) {
      resolve([]);
      return;
    }

    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then((value) => {
          results[index] = value;
          completed++;

          if (completed === promises.length) {
            resolve(results);
          }
        })
        .catch(reject);
    });
  });
}

🧪 Example

myPromiseAll([
  Promise.resolve("A"),
  Promise.resolve("B"),
  Promise.resolve("C")
]).then(console.log);

Output:

["A", "B", "C"]

🧠 Why Promise.resolve(promise)?

Promise.resolve(1)

allows:

myPromiseAll([
  Promise.resolve("A"),
  123,
  Promise.resolve("C")
]);

to work correctly.


🌍 Key Things Interviewers Expect

✅ Preserves result order

[
  Promise.resolve("A"),
  Promise.resolve("B")
]

Result:

["A", "B"]

Even if B finishes first.


✅ Rejects immediately

myPromiseAll([
  Promise.resolve("A"),
  Promise.reject("Error"),
  Promise.resolve("C")
]);

Output:

Error

⚡ One-line Interview Answer

Promise.all returns a single promise that waits for all input promises to resolve, preserves their order in the result array, and rejects immediately if any promise fails.


🚀 10-Second Version

Promise.all waits for all promises to complete, returns results in order, and fails fast if any promise rejects.


🧠 Memory Trick

"Count completions → store by index → resolve when count equals total → reject on first error."

This is usually enough to explain and implement Promise.all confidently in a frontend interview.

🧠 Simple Definition (Word-for-word)

Here’s a simple, interview-ready answer you can memorize easily .


⚡ Super Simple Line

🧪 Code setTimeout(() => console.log(1), 0); Promise.resolve().then(() => console.log(2)); console.log(3); ✅ Output 3 2 1 🧠 Why?


⚡ Key Details & Explanation

Here’s a simple, interview-ready answer you can memorize easily.


🧪 Code

setTimeout(() => console.log(1), 0);

Promise.resolve().then(() => console.log(2));

console.log(3);

✅ Output

3
2
1

🧠 Why?

Step 1: Synchronous code runs first

console.log(3);

Output:

3

Step 2: Promise goes to Microtask Queue

Promise.resolve().then(() => console.log(2));

Microtask Queue:

[ log(2) ]

Step 3: setTimeout goes to Macrotask Queue

setTimeout(() => console.log(1), 0);

Macrotask Queue:

[ log(1) ]

Step 4: Event Loop checks queues

Priority is:

Call Stack
↓
Microtasks (Promises)
↓
Macrotasks (setTimeout)

So:

2

runs before

1

⚡ Super Simple Rule

Sync code → Microtasks → Macrotasks


🧠 One-line Interview Answer

The output is 3, 2, 1 because synchronous code runs first, Promise callbacks are placed in the microtask queue, and setTimeout callbacks are placed in the macrotask queue. The event loop always executes microtasks before macrotasks.


🚀 10-Second Version

Output is 3, 2, 1. JavaScript runs synchronous code first, then Promises (microtasks), and finally timers (macrotasks).


🧠 Memory Trick

Sync → Promise → setTimeout

If you see:

console.log(...)
Promise.then(...)
setTimeout(...)

The order is almost always:

Sync first
Promise second
setTimeout last

⚡ One-line Interview Answer

Here’s a simple, interview-ready answer you can memorize easily .

Here’s a simple, interview-ready answer you can memorize easily.


🧠 Simple Definition

A shallow copy copies only the first level of an object. Nested objects are still shared by reference.
A deep copy copies everything, including nested objects, creating completely independent copies.


⚡ Super Simple Line

Shallow copy = copies top level only
Deep copy = copies everything recursively


🧪 Shallow Copy Example

const user = {
  name: "Titumir",
  address: {
    city: "Chattogram"
  }
};

const copy = { ...user };

copy.name = "John";
copy.address.city = "Dhaka";

console.log(user.address.city);

✅ Output

Dhaka

🧠 Why?

Because:

copy.address === user.address

is

true

The nested object is shared.


🧪 Deep Copy Example

const user = {
  name: "Titumir",
  address: {
    city: "Chattogram"
  }
};

const copy = structuredClone(user);

copy.address.city = "Dhaka";

console.log(user.address.city);

✅ Output

Chattogram

🧠 Why?

Deep copy creates a completely new nested object.

copy.address === user.address

is

false

🌍 Common Ways to Create Copies

Shallow Copy

const copy = { ...obj };
const copy = Object.assign({}, obj);

Deep Copy

const copy = structuredClone(obj);

🧠 Key Difference Table

FeatureShallow CopyDeep CopyTop-level properties✅ copied✅ copiedNested objects❌ shared✅ copiedChanges affect originalSometimesNoPerformanceFasterSlower


🌍 Real-World Use Case

Shallow Copy

React state updates:

setUser({
  ...user,
  name: "New Name"
});

Deep Copy

When you need to modify deeply nested data without affecting the original.


⚡ One-line Interview Answer

A shallow copy copies only the first level of an object and shares nested references, while a deep copy recursively copies all nested objects, making the new object completely independent.


🚀 10-Second Version

Shallow copy shares nested objects, deep copy creates completely independent copies of all nested objects.


🧠 Memory Trick

Shallow = surface only
Deep = everything copied


Quick Interview Check

const a = {
  x: {
    y: 1
  }
};

const b = { ...a };

b.x.y = 10;

console.log(a.x.y);

✅ Output

10

Because spread operator (...) creates a shallow copy, not a deep copy.

Here’s a simple, interview-ready answer you can memorize easily.


🧠 Simple Definition

Hoisting is JavaScript’s behavior of moving variable and function declarations to the top of their scope before execution.
The Temporal Dead Zone is the time between entering a scope and the actual declaration of let and const, where they exist but cannot be accessed.


⚡ Super Simple Line

Hoisting = declarations are lifted up
TDZ = block where let/const exist but cannot be used


🧪 1. Hoisting Example (var)

console.log(a);

var a = 10;

Output:

undefined

🧠 Why?

JS treats it like:

var a;
console.log(a);
a = 10;

🧪 2. Hoisting with Function

sayHello();

function sayHello() {
  console.log("Hello");
}

Output:

Hello

🧠 Why?

Function declarations are fully hoisted.


🚨 3. Temporal Dead Zone (let/const)

console.log(a);

let a = 10;

❌ Error:

ReferenceError: Cannot access 'a' before initialization

🧠 Why?

  • let is hoisted BUT NOT initialized

  • It stays in Temporal Dead Zone (TDZ)


📦 TDZ Visual Idea

Scope starts
   ↓
TDZ (cannot access variable)
   ↓
let a = 10;
   ↓
usable

🧪 4. const behaves same as let

console.log(b);

const b = 20;

❌ Error:

Same TDZ error


🧠 Key Difference

FeaturevarletconstHoistedyesyesyesInitializedyes (undefined)nonoTDZnoyesyesAccess before declarationundefinederrorerror


🌍 Real-World Mental Model

JS first “registers” all variables, but only var gets initial value (undefined).
let and const exist but are locked in a temporary unsafe zone (TDZ) until execution reaches them.


⚡ One-line Interview Answer

Hoisting is JavaScript’s behavior of moving declarations to the top of their scope, while the Temporal Dead Zone is the period between entering scope and initialization of let and const, during which they cannot be accessed.


🚀 10-Second Version

Hoisting moves declarations to the top. TDZ is the time between declaration and initialization for let and const where they cannot be accessed.


🧠 Memory Trick

Hoisting = lifted
TDZ = locked until initialized


Here’s a simple, interview-ready answer you can memorize easily.


🧠 Simple Definition

TypeScript narrows types by using control flow analysis to refine a variable’s type based on conditions like typeof, instanceof, equality checks, and custom type guards.


⚡ Super Simple Line

Type narrowing = TypeScript figuring out the exact type based on checks in code.


🧪 Simple Example (typeof narrowing)

function print(value: string | number) {
  if (typeof value === "string") {
    console.log(value.toUpperCase());
  } else {
    console.log(value.toFixed(2));
  }
}

🧠 What is happening?

  • Initially: value = string | number

  • After check:

    • inside if → string

    • inside else → number

👉 TypeScript automatically narrows it


🧪 2. Equality Narrowing

type A = { type: "A"; a: string };
type B = { type: "B"; b: number };

function handle(obj: A | B) {
  if (obj.type === "A") {
    console.log(obj.a);
  } else {
    console.log(obj.b);
  }
}

🧠 Why it works?

  • type is a discriminant

  • TS uses it to narrow the union type


🧪 3. instanceof Narrowing

function logDate(value: Date | string) {
  if (value instanceof Date) {
    console.log(value.toISOString());
  } else {
    console.log(value.toUpperCase());
  }
}

🧠 4. Truthy / Falsy Narrowing

function print(value?: string) {
  if (value) {
    console.log(value.toUpperCase());
  }
}

🧠 5. Custom Type Guard (Advanced but important)

type Dog = { bark: () => void };
type Cat = { meow: () => void };

function isDog(animal: Dog | Cat): animal is Dog {
  return (animal as Dog).bark !== undefined;
}

function speak(animal: Dog | Cat) {
  if (isDog(animal)) {
    animal.bark();
  } else {
    animal.meow();
  }
}

🌍 Real Idea Behind Narrowing

TypeScript doesn’t “change types at runtime” — it just analyzes your conditions and narrows the possible type in each code path.


⚡ One-line Interview Answer

TypeScript narrows types using control flow analysis based on checks like typeof, instanceof, equality checks, truthiness, and custom type guards to refine union types into more specific types.


🚀 10-Second Version

Type narrowing is TypeScript refining a union type into a specific type using runtime checks like typeof, instanceof, or custom guards.


🧠 Memory Trick

“Check → TS reduces → safer type”


Here’s a simple, interview-ready answer you can memorize easily.


🧠 Simple Definition

call, apply, and bind are JavaScript methods used to explicitly set the value of this and control how a function is executed.


⚡ Super Simple Line

call = run now with args one by one
apply = run now with args as array
bind = returns new function for later use


🧪 Example Function

function greet(age) {
  console.log(this.name, age);
}

const user = { name: "A" };

🔵 1. call

greet.call(user, 25);

Output:

A 25

🧠 Meaning:

  • Executes immediately

  • Arguments passed individually


🟡 2. apply

greet.apply(user, [25]);

Output:

A 25

🧠 Meaning:

  • Executes immediately

  • Arguments passed as array


🟢 3. bind

const boundFn = greet.bind(user, 25);

boundFn();

Output:

A 25

🧠 Meaning:

  • Does NOT execute immediately

  • Returns a new function


🧠 Key Difference Table

MethodExecutes immediatelyArguments formatReturnscallyescomma separatedresultapplyyesarrayresultbindnocomma separatednew function


🌍 Real-World Use Case

👉 Losing this in callbacks

const user = {
  name: "A",
  show() {
    console.log(this.name);
  }
};

setTimeout(user.show, 1000);

👉 Output: undefined


Fix using bind:

setTimeout(user.show.bind(user), 1000);

👉 Output: A


🧠 Why this happens?

Because function loses its original object reference when passed.


⚡ One-line Interview Answer

call, apply, and bind are used to explicitly set the value of this. call and apply execute the function immediately (apply takes array arguments), while bind returns a new function that can be executed later with a fixed this.


🚀 10-Second Version

call and apply run functions immediately with a specific this, while bind returns a new function with this permanently set.


🧠 Memory Trick

call = comma
apply = array
bind = bind later


Here’s a simple, interview-ready answer you can memorize easily.


🧠 Simple Definition

Event bubbling and capturing are two ways events travel in the DOM tree, and event delegation is a technique that uses bubbling to handle events efficiently.


⚡ Super Simple Line

Capturing = top → target
Bubbling = target → top
Delegation = handle events at parent using bubbling


🌳 1. Event Capturing (Trickling Down)

Event goes from parent → child → target

Document → HTML → Body → Parent → Child (target)

Code:

element.addEventListener("click", () => {
  console.log("Captured");
}, true); // 👈 capturing mode

🫧 2. Event Bubbling (Default)

Event goes from target → parent → root

Child → Parent → Body → Document

Code:

element.addEventListener("click", () => {
  console.log("Bubbling");
}); // default is bubbling

🧠 Example Flow

If you click a button inside a div:

<div>
  <button>Click</button>
</div>

Order:

Capturing:   div → button  
Target:      button  
Bubbling:    button → div

🚫 Stop Bubbling

event.stopPropagation();

👉 Stops event from going up


🧩 3. Event Delegation (VERY IMPORTANT)

Instead of adding event listeners to each child, you attach one listener to the parent and handle events using bubbling.


🧪 Example

<ul id="list">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>

document.getElementById("list").addEventListener("click", (e) => {
  console.log(e.target.innerText);
});

🧠 Why this works?

  • Click happens on <li>

  • Event bubbles up to <ul>

  • Parent handles all clicks


🌍 Why delegation is useful?

  • Better performance

  • Less memory usage

  • Works for dynamic elements


⚡ Key Differences Table

ConceptDirectionPurposeCapturingtop → bottomevent travels downBubblingbottom → topevent travels upDelegationuses bubblinghandle events at parent


⚡ One-line Interview Answer

Event capturing is when the event flows from the root to the target, bubbling is when it flows from the target back to the root, and event delegation is a technique where we use event bubbling to handle events at a parent level instead of attaching listeners to each child.


🚀 10-Second Version

Capturing goes top-down, bubbling goes bottom-up, and delegation uses bubbling to handle child events from a parent.


🧠 Memory Trick

“Down = capturing, Up = bubbling, Parent = delegation”


🧠 Simple Definition (Word-for-word)

CORS (Cross-Origin Resource Sharing) is a browser security mechanism that restricts a web application from requesting resources from a domain different from the one that served it. It happens because browsers enforce the Same-Origin Policy (SOP) to protect users from malicious requests, and it is resolved by configuring the server to send CORS headers permitting cross-origin access.


⚡ Super Simple Line

CORS = browser blocks cross-origin requests unless the target server explicitly sends "allow" headers.


🛠️ Configuring CORS Properly

Configuring CORS requires setting appropriate headers on the backend. Here is how to configure it securely using Node.js/Express:


Secure Express Middleware Configuration:

const express = require("express");
const cors = require("cors");
const app = express();

const whitelist = ["https://trustedfrontend.com", "https://admin.trustedfrontend.com"];
const corsOptions = {
  origin: function (origin, callback) {
    // Allow requests with no origin (like mobile apps or curl)
    if (!origin || whitelist.indexOf(origin) !== -1) {
      callback(null, true);
    } else {
      callback(new Error("Not allowed by CORS"));
    }
  },
  methods: ["GET", "POST", "PUT", "DELETE"],
  allowedHeaders: ["Content-Type", "Authorization"],
  credentials: true // Allow cookies to be sent
};

app.use(cors(corsOptions));

🔥 Key CORS Headers

  • Access-Control-Allow-Origin: Specifies which origins are allowed to access resources.

  • Access-Control-Allow-Methods: Dictates permitted HTTP methods (GET, POST, etc.).

  • Access-Control-Allow-Headers: Dictates custom headers allowed in requests.

  • Access-Control-Allow-Credentials: Determines whether cookies or authorization headers can be exposed.


⚡ One-line Interview Answer

CORS is a browser-enforced security check that blocks cross-origin requests unless the backend server responds with specific headers allowing that origin's access.

🧠 Simple Definition

localStorage, sessionStorage, and cookies are all ways to store data in the browser, but they differ in lifespan, size, and how they are sent to the server.


⚡ Super Simple Line

  • localStorage = permanent storage

  • sessionStorage = temporary (tab-based)

  • cookies = small storage sent to server


🧪 1. localStorage

localStorage.setItem("user", "A");

console.log(localStorage.getItem("user"));

🧠 Key Points:

  • Stays even after closing browser

  • No expiry (until manually deleted)

  • ~5–10MB storage


🧪 2. sessionStorage

sessionStorage.setItem("user", "A");

console.log(sessionStorage.getItem("user"));

🧠 Key Points:

  • Data is lost when tab is closed

  • Works per tab

  • Not shared between tabs


🧪 3. Cookies

document.cookie = "user=A";

🧠 Key Points:

  • Sent with every HTTP request

  • Has expiry time

  • Very small (~4KB)

  • Used for authentication


🌍 Real-World Use Cases

🟢 localStorage

  • Theme settings (dark/light mode)

  • User preferences

  • Cached data


🟡 sessionStorage

  • Form data during a session

  • Temporary login steps


🔴 cookies

  • Login sessions (auth tokens)

  • Server communication

  • Tracking analytics


🧠 Key Differences Table

FeaturelocalStoragesessionStoragecookiesLifetimepermanenttab-basedconfigurableServer sent❌ No❌ No✅ YesSizelarge (~5MB)large (~5MB)small (~4KB)Scopeall tabssingle taball requests


🧠 Important Insight

localStorage and sessionStorage are only for client-side storage, but cookies are automatically included in HTTP requests.


⚡ One-line Interview Answer

localStorage stores data permanently in the browser, sessionStorage stores data temporarily for a single tab session, and cookies store small data that is sent with every HTTP request, often used for authentication.


🚀 10-Second Version

localStorage is permanent, sessionStorage is temporary per tab, and cookies are small pieces of data sent to the server with requests.


🧠 Memory Trick

local = long life
session = single session
cookie = goes to server


🧠 Simple Definition (Word-for-word)

A higher-order function (HOF) is a function that accepts another function as an argument, returns a function, or both. Function currying is the technique of translating a function that takes multiple arguments into a sequence of nested unary functions. Function composition is the process of combining two or more functions to produce a new function (e.g., executing them in sequence where the output of one feeds into the next).


⚡ Super Simple Line

HOF = functions acting on other functions.
Currying = nesting arguments one by one.
Composition = chaining functions together like a pipeline.


🧪 Code Examples

// 1. Higher-Order Function
const runTwice = (fn, val) => fn(fn(val));
const addThree = x => x + 3;
console.log(runTwice(addThree, 1)); // ✅ 7

// 2. Currying Function
const multiply = a => b => a * b;
const double = multiply(2);
console.log(double(5)); // ✅ 10

// 3. Function Composition
const compose = (f, g) => x => f(g(x));
const uppercase = str => str.toUpperCase();
const exclaim = str => `${str}!`;
const shout = compose(exclaim, uppercase);
console.log(shout("hello")); // ✅ "HELLO!"

🌍 Benefits & Use Cases

  • Reusability & DRY: HOFs like map, filter, and reduce let us abstract loops and logic.

  • Configurability: Currying allows you to partially configure arguments (e.g., logging levels, API configurations) and reuse the preconfigured function.

  • Modular Pipelines: Composition lets you chain small, pure, single-purpose functions to compose complex transformations cleanly.


⚡ One-line Interview Answer

HOFs treat functions as first-class values, currying breaks arguments into sequential functions, and composition pipes their outputs to build highly reusable, declarative code.

🧠 Simple Definition

A memory leak in JavaScript happens when memory that is no longer needed is still being held in memory and cannot be garbage collected, causing the application to slowly consume more and more memory.


⚡ Super Simple Line

Memory leak = memory not freed even when it’s no longer needed


🧠 Why it happens?

JavaScript has garbage collection, but memory leaks happen when we accidentally keep references to unused data.


🌍 Simple Mental Picture

You keep putting trash in a room, but never let the cleaner remove it → room gets full.


🧪 1. Global Variables (Common leak)

function leak() {
  hugeData = new Array(1000000).fill("data");
}

leak();

🧠 Problem:

  • hugeData becomes global

  • Never removed from memory


🧪 2. Forgotten Timers

setInterval(() => {
  console.log("Running...");
}, 1000);

🧠 Problem:

  • Interval keeps running forever

  • Even if not needed


🧪 3. Unremoved Event Listeners

function attach() {
  document.addEventListener("click", () => {
    console.log("Clicked");
  });
}

attach();

🧠 Problem:

  • Listener stays in memory

  • Even if element/page is removed


🧪 4. Closures Holding Memory

function outer() {
  let bigData = new Array(1000000);

  return function inner() {
    console.log(bigData[0]);
  };
}

const fn = outer();

🧠 Problem:

  • inner keeps reference to bigData

  • Garbage collector cannot free it


🧠 Key Causes Summary

  • Global variables

  • Uncleared timers (setInterval)

  • Unremoved event listeners

  • Closures holding large data

  • Cached objects not cleaned


🌍 Real-World Impact

  • Slow performance

  • Browser freezing

  • Increased memory usage

  • App crashes in long sessions


🛠️ How to prevent memory leaks

  • Use clearInterval / clearTimeout

  • Remove event listeners

  • Avoid unnecessary globals

  • Be careful with closures

  • Use WeakMap/WeakSet when appropriate


⚡ One-line Interview Answer

A memory leak in JavaScript occurs when unused memory is not released due to lingering references like global variables, event listeners, timers, or closures, causing the application to consume increasing memory over time.


🚀 10-Second Version

Memory leak happens when unused data is still referenced in memory, preventing garbage collection, often due to globals, timers, closures, or event listeners.


🧠 Memory Trick

“Still referenced = still alive = memory leak”


🧠 Simple Definition

In JavaScript, primitive values are passed by value, meaning a copy is passed.
Objects and arrays are passed by reference-like behavior, meaning a reference to the original memory is passed.


⚡ Super Simple Line

  • Pass-by-value = copy is passed

  • Pass-by-reference (behavior) = same object is shared


🧪 1. Pass-by-Value (Primitive types)

let a = 10;

let b = a;

b = 20;

console.log(a);

✅ Output:

10

🧠 Why?

  • a is copied into b

  • Changing b does NOT affect a

👉 Works for:

  • number

  • string

  • boolean

  • null

  • undefined

  • symbol


🧪 2. Objects (Reference behavior)

let obj1 = { name: "A" };

let obj2 = obj1;

obj2.name = "B";

console.log(obj1.name);

✅ Output:

B

🧠 Why?

  • Both variables point to the SAME object in memory

  • Changing one affects the other


🌍 Simple Mental Model

Primitive → copied box
Object → shared address

🧪 3. Arrays (also reference)

let arr1 = [1, 2, 3];

let arr2 = arr1;

arr2.push(4);

console.log(arr1);

Output:

[1, 2, 3, 4]

🧠 Important Clarification (Interview trick)

👉 JavaScript is NOT true pass-by-reference
👉 It is actually pass-by-value of reference


🧠 What does that mean?

  • The reference itself is copied

  • But it still points to same memory


🌍 Real-World Example

function update(obj) {
  obj.name = "Changed";
}

const user = { name: "A" };

update(user);

console.log(user.name);

Output:

Changed

🧠 Key Difference Table

TypeBehaviorExamplePrimitivepass-by-value (copy)number, stringObjectpass-by-reference-likeobject, array


⚡ One-line Interview Answer

In JavaScript, primitive values are passed by value, meaning a copy is passed, while objects and arrays are passed by sharing a reference, so changes inside a function affect the original object.


🚀 10-Second Version

Primitives are copied when passed, but objects and arrays share the same reference, so changes affect the original.


🧠 Memory Trick

Primitive = copy
Object = shared memory


🧠 Simple Definition (Word-for-word)

The Logical OR (||) operator returns the right-hand operand if the left-hand operand is any falsy value (such as 0, "", false, null, undefined, or NaN). The Nullish Coalescing (??) operator only returns the right-hand operand if the left-hand operand is nullish (specifically null or undefined).


⚡ Super Simple Line

|| triggers on any falsy value (0, "", false, etc.).
?? triggers only on null or undefined.


🧪 Simple Examples

// 1. Handling Number 0
const count = 0;
console.log(count || 10); // ✅ 10 (0 is falsy, so it defaults)
console.log(count ?? 10); // ✅ 0  (0 is not nullish, so it is kept)

// 2. Handling Empty String
const text = "";
console.log(text || "default"); // ✅ "default" (empty string is falsy)
console.log(text ?? "default"); // ✅ "" (empty string is not nullish)

// 3. Handling Boolean False
const active = false;
console.log(active || true);  // ✅ true
console.log(active ?? true);  // ✅ false (keeps the false flag)

// 4. Handling Null/Undefined (Same behavior)
const user = null;
console.log(user || "guest"); // ✅ "guest"
console.log(user ?? "guest"); // ✅ "guest"

📊 Key Difference Table

Left-Hand ValueLogical OR (||) ResultNullish Coalescing (??) Result
nullreturns right-hand sidereturns right-hand side
undefinedreturns right-hand sidereturns right-hand side
0returns right-hand side ⚠️keeps 0 (left-hand side)
"" (empty string)returns right-hand side ⚠️keeps "" (left-hand side)
falsereturns right-hand side ⚠️keeps false (left-hand side)
NaNreturns right-hand side ⚠️keeps NaN (left-hand side)

⚡ One-line Interview Answer

The || operator defaults on any falsy value, whereas the ?? operator defaults only on null and undefined, allowing you to safely preserve valid falsy values like 0, false, and empty strings.


🚀 10-Second Version

Use ?? to set default configurations or configuration overrides without accidentally discarding valid falsy values like 0, false, or empty strings.