Here’s a simple, muscle-memory friendly interview answer you can reuse directly.
🧠 Simple Definition (Word-for-word ready)
var,let, andconstare ways to declare variables in JavaScript. The main differences are scope, re-declaration, and reassignment.varis function-scoped and can be re-declared.letis block-scoped and can be reassigned but not re-declared in the same scope.constis 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
| Feature | Prototypal Inheritance | Classical Inheritance |
|---|---|---|
| Source of Inheritance | Directly from other objects (Prototypes) | From Class blueprints / schemas |
| Under the Hood | Prototype chain (dynamic pointers) | Class hierarchies (structural cloning/lookup) |
| Flexibility | Objects can be modified at runtime directly | Classes are static and defined compile-time |
| Memory Efficiency | Highly 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 returnstrueonly 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
| Expression | Loose Equality (==) | Strict Equality (===) |
|---|---|---|
5 == "5" | true | false |
null == undefined | true | false |
0 == false | true | false |
[] == ![] | true | false |
⚡ 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/awaitis a cleaner way to work with Promises. Under the hood,asyncfunctions return a Promise, andawaitpauses execution until the Promise is resolved, without blocking the main thread.
⚡ Super Simple Line
async/await = Promises made readable, where
awaitpauses 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:
test()starts → runs sync code → printsAawaitis hit → function PAUSES- Rest of JS continues → prints
C - 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:
- Before await → runs immediately
- 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
- Avoids Promise Nesting ("Callback Hell"): Allows writing async logic sequentially from top to bottom, avoiding deep nesting of
.then()callbacks. - Native Error Handling: Standard synchronous
try/catchblocks can handle both synchronous runtime errors and asynchronous network errors in one unified place. - Intuitive Flow: Conditional branches and loops (e.g.
for...ofloops withawait) work exactly like synchronous code, rather than requiring complex recursion or helper libraries. - 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)
WeakMapandWeakSetare 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
userobject 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
- 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
- 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
⚡ 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 Type | What 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, andneverare special types used to control how flexible or strict a value can be.anydisables type checking,unknownis a safe version of any, andneverrepresents 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?
typeis 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?
asyncautomatically wraps the return value in a PromiseSo 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 asPromise<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:
Try to convert operands to primitive values
If one side becomes a string, perform string concatenation
Otherwise, perform numeric addition
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.alltakes 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
Wait for all promises
Store results in correct order
Resolve when all are done
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, 1because synchronous code runs first, Promise callbacks are placed in the microtask queue, andsetTimeoutcallbacks 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 ofletandconst, where they exist but cannot be accessed.
⚡ Super Simple Line
Hoisting = declarations are lifted up
TDZ = block wherelet/constexist 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?
letis hoisted BUT NOT initializedIt 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
vargets initial value (undefined).letandconstexist 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
letandconst, 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 | numberAfter check:
inside
if→ stringinside
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?
typeis a discriminantTS 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, andbindare JavaScript methods used to explicitly set the value ofthisand 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 fixedthis.
🚀 10-Second Version
call and apply run functions immediately with a specific
this, while bind returns a new function withthispermanently 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, andreducelet 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:
hugeDatabecomes globalNever 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:
innerkeeps reference tobigDataGarbage 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/clearTimeoutRemove 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?
ais copied intobChanging
bdoes NOT affecta
👉 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 as0,"",false,null,undefined, orNaN). The Nullish Coalescing (??) operator only returns the right-hand operand if the left-hand operand is nullish (specificallynullorundefined).
⚡ 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 Value | Logical OR (||) Result | Nullish Coalescing (??) Result |
|---|---|---|
null | returns right-hand side | returns right-hand side |
undefined | returns right-hand side | returns right-hand side |
0 | returns right-hand side ⚠️ | keeps 0 (left-hand side) |
"" (empty string) | returns right-hand side ⚠️ | keeps "" (left-hand side) |
false | returns right-hand side ⚠️ | keeps false (left-hand side) |
NaN | returns right-hand side ⚠️ | keeps NaN (left-hand side) |
⚡ One-line Interview Answer
The
||operator defaults on any falsy value, whereas the??operator defaults only onnullandundefined, allowing you to safely preserve valid falsy values like0,false, and empty strings.
🚀 10-Second Version
Use
??to set default configurations or configuration overrides without accidentally discarding valid falsy values like0,false, or empty strings.