🧠 Simple Definition (Word-for-word)
Node.js executes JavaScript on a single thread using the event loop to delegate heavy asynchronous tasks (like database queries and filesystem operations) to the operating system or libuv thread pool. Concurrency is the event loop managing multiple tasks in overlapping timeframes by context switching. Parallelism is executing multiple tasks simultaneously across different CPU cores, achieved in Node.js via worker threads or child processes.
⚡ Super Simple Line
Concurrency = Event Loop handling many network requests (waiting).
Parallelism = Worker Threads executing heavy computations (processing) on separate CPU cores.
🧪 Code Example: Worker Threads (Parallelism)
// main.js
const { Worker } = require("worker_threads");
function runHeavyMath(data) {
return new Promise((resolve, reject) => {
const worker = new Worker("./worker.js", { workerData: data });
worker.on("message", resolve);
worker.on("error", reject);
});
}
// worker.js
const { parentPort, workerData } = require("worker_threads");
// Run CPU intensive task on separate CPU thread
let result = 0;
for (let i = 0; i < workerData.limit; i++) result += i;
parentPort.postMessage(result);
⚙️ Libuv and OS Delegation
When you call an asynchronous function like fs.readFile or fetch:
Node.js passes the task to libuv.
Libuv delegates network tasks directly to the OS kernel (non-blocking system calls).
Libuv uses its internal Thread Pool (default size: 4) to execute blocking tasks (e.g., cryptography, hashing, file systems).
Once complete, the callback queue triggers and the Event Loop pushes the callback back to the main thread.
⚡ One-line Interview Answer
Node.js runs single-threaded concurrent JavaScript using the event loop for non-blocking I/O operations, while worker threads enable parallel execution of CPU-heavy operations across multiple CPU cores.
🧠 Simple Definition
Worker threads in Node.js are a way to run JavaScript code in parallel on multiple threads, separate from the main thread, to handle CPU-intensive tasks without blocking the event loop.
⚡ Super Simple Line
Worker threads = extra JS threads for heavy tasks so main thread stays free
🧠 Why they exist?
Node.js is:
single-threaded for JS execution
So if you do heavy work:
CPU-heavy task → blocks event loop → slows everything
Worker threads solve this.
🧪 Simple Example
const { Worker } = require("worker_threads");
const worker = new Worker(`
const { parentPort } = require("worker_threads");
let sum = 0;
for (let i = 0; i < 1e9; i++) sum++;
parentPort.postMessage(sum);
`, { eval: true });
worker.on("message", (result) => {
console.log("Result:", result);
});
🧠 What is happening?
Main thread creates worker
Worker runs heavy loop separately
Main thread stays free
Worker sends result back
🌍 Simple Mental Model
Main Thread (Event Loop)
|
| creates worker
↓
Worker Thread (heavy CPU work)
↓
returns result → Main Thread
🧪 When NOT needed
❌ I/O tasks like:
database calls
file reading
API requests
👉 Node already handles these with libuv
🧠 When to use worker threads
🔥 CPU-heavy tasks:
Image processing
Video encoding
Cryptography (hashing, encryption)
Large data processing
Complex calculations
🧪 Real Example Use Case
function heavyTask() {
let sum = 0;
for (let i = 0; i < 1e10; i++) sum++;
return sum;
}
👉 Without worker thread:
Blocks entire server
👉 With worker thread:
Runs in parallel safely
🧠 Key Insight
Worker threads are NOT for async I/O — they are for CPU-bound tasks.
🔥 Worker Threads vs Event Loop
| Feature | Event Loop | Worker Threads |
|---|---|---|
| Threads | 1 | multiple |
| Best for | I/O tasks | CPU-heavy tasks |
| Blocking risk | yes | no |
| Parallel execution | no | yes |
⚡ One-line Interview Answer
Worker threads in Node.js allow running JavaScript in parallel on separate threads to handle CPU-intensive tasks without blocking the main event loop, which is otherwise single-threaded.
🚀 10-Second Version
Worker threads let Node.js run heavy CPU tasks in parallel threads so the main event loop stays non-blocking and responsive.
🧠 Memory Trick
Event loop = handles I/O
Worker threads = handles heavy CPU work
🧠 Simple Definition
process.nextTick()andsetImmediate()are both ways to schedule async code in Node.js, but they run at different phases of the event loop.
⚡ Super Simple Line
nextTick= run before anything else (highest priority)setImmediate= run after I/O events (next phase)
🧪 Simple Example
console.log("Start");
process.nextTick(() => {
console.log("nextTick");
});
setImmediate(() => {
console.log("setImmediate");
});
console.log("End");
✅ Output
Start
End
nextTick
setImmediate
🧠 Why?
Step-by-step:
Synchronous code runs first:
Start → End
process.nextTick()goes to microtask queue (highest priority)setImmediate()goes to check phase queueEvent loop order:
Sync code
↓
nextTick queue
↓
I/O callbacks
↓
setImmediate queue
🧠 Key Idea
🔵 process.nextTick
Runs immediately after current function finishes
Before Promises and before event loop continues
Can starve event loop if misused
🟢 setImmediate
Runs in next iteration of event loop
After I/O callbacks
More predictable timing
🌍 Real Mental Model
Call Stack
↓
nextTick queue (VIP priority)
↓
Promise microtasks
↓
I/O callbacks
↓
setImmediate
🧪 Another Example (I/O case)
const fs = require("fs");
fs.readFile("file.txt", () => {
setImmediate(() => {
console.log("setImmediate");
});
process.nextTick(() => {
console.log("nextTick");
});
});
✅ Output
nextTick
setImmediate
🧠 Why?
Inside I/O callback:
nextTickruns immediately after current executionsetImmediateruns in next phase
🔥 Key Difference Table
FeaturenextTicksetImmediatePhasebefore event loop continuesafter I/O phasePriorityhighestlowerTimingimmediate (after current code)next loop iterationRiskcan block event loopsafe
⚡ One-line Interview Answer
process.nextTick runs immediately after the current operation before the event loop continues and has higher priority, while setImmediate runs in the next iteration of the event loop after I/O callbacks.
🚀 10-Second Version
nextTick runs before the event loop continues, while setImmediate runs in the next event loop cycle after I/O events.
🧠 Memory Trick
nextTick = NOW (priority VIP)
setImmediate = NEXT cycle
🧠 Simple Definition (Word-for-word)
A Buffer is a temporary chunk of physical memory allocated to store raw binary data outside the V8 engine. A Stream is a continuous flow of data broken down into smaller, digestible chunks, permitting files to be read or written progressively without loading the entire content into RAM. The four main types of streams in Node.js are Readable, Writable, Duplex, and Transform.
⚡ Super Simple Line
Buffer = a fixed-size container for binary data.
Stream = a pipe that moves data chunk-by-chunk to save memory.
🧪 Code Example: Stream Piping with Transform
const fs = require("fs");
const zlib = require("zlib");
// Pipe a readable stream through a transform stream (gzip) into a writable stream
fs.createReadStream("largefile.txt")
.pipe(zlib.createGzip()) // Transform stream
.pipe(fs.createWriteStream("largefile.txt.gz")) // Writable stream
.on("finish", () => console.log("File compressed successfully."));
📦 The Four Stream Types
Readable: Abstraction for a source from which data can be consumed (e.g.,
fs.createReadStream, HTTP request).Writable: Abstraction for a destination to which data can be written (e.g.,
fs.createWriteStream, HTTP response).Duplex: A stream that is both Readable and Writable (e.g., TCP socket).
Transform: A duplex stream where the output is computed based on input modification (e.g.,
zlib.createGzip, crypto streams).
⚡ One-line Interview Answer
Buffers store raw binary data in memory, while Streams process data in smaller sequential chunks to enable efficient reading/writing of large datasets without exhausting RAM.
🧠 Simple Definition
In a long-running Node.js process, memory leaks are handled by finding and removing unintended references that prevent garbage collection, and by properly cleaning up resources like timers, listeners, and caches.
⚡ Super Simple Line
Fix memory leaks = remove unused references + clean up resources + monitor memory
🌍 Why it matters?
In long-running apps (like APIs, servers):
Memory keeps increasing → app slows down → crash eventually
🧪 Common Memory Leak Sources
🟥 1. Forgotten Event Listeners
emitter.on("data", () => {
console.log("data received");
});
❌ Problem:
Listener stays forever
✅ Fix:
emitter.removeListener("data", handler);
🟧 2. Uncleared Timers
setInterval(() => {
console.log("running");
}, 1000);
❌ Problem:
Runs forever
✅ Fix:
clearInterval(timer);
🟨 3. Growing Caches
const cache = {};
function store(key, value) {
cache[key] = value;
}
❌ Problem:
Cache grows infinitely
✅ Fix:
Use LRU cache
Set size limits
🟩 4. Large Closures
function outer() {
let bigData = new Array(1e6);
return function inner() {
console.log(bigData[0]);
};
}
❌ Problem:
bigData never gets freed
🟦 5. Global Variables
global.data = hugeObject;
❌ Problem:
Never garbage collected
🛠️ How to handle memory leaks (REAL STRATEGY)
🧹 1. Clean up resources
clearTimeoutclearIntervalremoveEventListenerclose DB connections
🧠 2. Avoid unnecessary references
don’t store large objects globally
nullify unused references
obj = null;
📊 3. Monitor memory usage
Use:
node --inspect app.js
or Chrome DevTools:
Heap snapshot
Allocation timeline
🧪 4. Use WeakMap / WeakSet
const cache = new WeakMap();
👉 allows garbage collection automatically
⚡ 5. Restart strategy (production)
PM2 auto restart
Docker container restart policies
🌍 Real Mental Model
Find leak → remove reference → let GC clean memory
⚡ One-line Interview Answer
Memory leaks in Node.js are handled by identifying and removing unnecessary references such as event listeners, timers, closures, and global objects, and by using tools like heap profiling and WeakMap to ensure proper garbage collection in long-running processes.
🚀 10-Second Version
Memory leaks are fixed by cleaning up timers, event listeners, and references so garbage collector can free memory, and by monitoring memory usage using profiling tools.
🧠 Memory Trick
“Leak = unwanted reference → Fix = remove reference”
🧠 Simple Definition (Word-for-word)
A RESTful API is an architectural style for design APIs that conforms to the constraints of REST (Representational State Transfer). It relies on standard HTTP methods (GET, POST, PUT, DELETE) to manipulate stateless resources represented as JSON or XML. Its core constraints are: Client-Server separation, Statelessness, Cacheability, Uniform Interface, Layered System, and Code-on-Demand.
⚡ Super Simple Line
RESTful API = a standardized architectural design where client and server communicate statelessly using standard HTTP actions on resources.
📋 Core Architectural Constraints
Statelessness: Every request from client to server must contain all the information necessary to understand and complete the request. The server must not store session context.
Uniform Interface: Simplifies architecture by standardizing interactions (e.g., using nouns like
/api/usersand HTTP verbs, utilizing self-descriptive messages and HATEOAS).Client-Server Separation: Decouples the frontend (UI state) from the backend (data storage), allowing them to scale independently.
Cacheability: Responses must explicitly define themselves as cacheable or non-cacheable to improve network performance.
🧪 RESTful Resource Mapping
| HTTP Method | Endpoint | Semantic Action | Success Code |
|---|---|---|---|
GET | /api/books | Retrieve list of books | 200 OK |
POST | /api/books | Create a new book | 201 Created |
PUT | /api/books/:id | Replace book entirely | 200 OK |
DELETE | /api/books/:id | Remove book | 204 No Content |
⚡ One-line Interview Answer
RESTful APIs are stateless, resource-oriented Web services that use standard HTTP methods and uniform path conventions to separate client UI logic from database storage.
🧠 Simple Definition
API versioning is the practice of managing changes in an API by creating different versions so existing clients don’t break when the API evolves.
⚡ Super Simple Line
Versioning = updating API without breaking old users
🌍 Why it is needed?
If you change API:
/users → now returns different structure
👉 Old apps may break
So we use versions:
/v1/users
/v2/users
🧪 Common Ways to Handle API Versioning
🟢 1. URL Versioning (Most common)
/api/v1/users
/api/v2/users
👍 Pros:
Very clear
Easy to debug
Most widely used
👎 Cons:
URL changes when version changes
🔵 2. Header Versioning
GET /users
Accept: application/vnd.myapi.v2+json
👍 Pros:
Clean URLs
Flexible
👎 Cons:
Harder to test/debug
🟡 3. Query Parameter Versioning
/users?version=1
/users?version=2
👍 Pros:
Simple to implement
👎 Cons:
Not very RESTful
Can be ignored accidentally
🟣 4. Subdomain Versioning
v1.api.example.com
v2.api.example.com
👍 Pros:
Strong separation
👎 Cons:
Complex setup
🌍 Real-World Example
Version 1:
{
"name": "A"
}
Version 2:
{
"fullName": "A",
"age": 25
}
👉 Instead of breaking old apps:
keep
/v1introduce
/v2
🧠 Best Practice (Interview Important)
Keep older versions stable
Deprecate gradually
Communicate changes clearly
Don’t modify breaking APIs in-place
🔥 Real Industry Approach
Most companies use:
/api/v1/...
/api/v2/...
👉 Because it's simple and predictable
⚡ One-line Interview Answer
API versioning is the strategy of managing changes in an API by introducing different versions (like v1, v2) so that existing clients are not broken when the API evolves.
🚀 10-Second Version
API versioning allows us to update APIs safely by creating multiple versions like v1 and v2 so old clients continue working.
🧠 Memory Trick
“New change = new version, old users = still safe”
🧠 Simple Definition
Authentication is the process of verifying who a user is. Authorization is the process of verifying what a user is allowed to do.
⚡ Super Simple Line
Authentication = “Who are you?”
Authorization = “What can you do?”
🧪 Simple Example
Login (Authentication)
User enters username + password
→ system verifies identity
→ user is logged in
Access (Authorization)
User tries to delete a post
→ system checks role/permission
→ allow or deny
🌍 Real-World Analogy
Imagine a building:
🪪 Authentication = showing ID card at entrance
🔑 Authorization = which rooms you are allowed to enter
🧪 Code Example
// Authentication
if (user.email === dbUser.email && user.password === dbUser.password) {
console.log("Logged in");
}
// Authorization
if (user.role === "admin") {
console.log("Can delete users");
}
🧠 Key Difference Table
FeatureAuthenticationAuthorizationMeaningVerify identityVerify permissionsQuestionWho are you?What can you do?HappensFirstAfter loginExamplelogin passwordrole check
🌍 Real-World Flow
1. User logs in → Authentication
2. System identifies user
3. User requests action → Authorization check
4. Allow / deny access
🧠 Important Insight
You must be authenticated before you can be authorized.
⚡ One-line Interview Answer
Authentication verifies the identity of a user, while authorization determines what actions or resources that authenticated user is allowed to access.
🚀 10-Second Version
Authentication checks who you are, and authorization checks what you are allowed to do.
🧠 Memory Trick
AuthN = Name (identity)
AuthZ = Zone (permissions)
🧠 Simple Definition (Word-for-word)
A JWT (JSON Web Token) is a compact, URL-safe container format used to securely transmit claims between client and server, composed of a Header, Payload, and Signature. Because JWTs are stateless, they pose security risks regarding revocation. In token auth, security is optimized by storing short-lived Access Tokens in memory, and utilizing secure, HTTP-only, SameSite cookies to store Refresh Tokens which rotate and request new access tokens when they expire.
⚡ Super Simple Line
Access Token = short-lived (5-15m), used for API calls.
Refresh Token = long-lived (7-30d), kept in secure httpOnly cookies, used to renew access tokens.
🔒 Storage Tradeoffs: LocalStorage vs HttpOnly Cookies
| Storage Option | Primary Vulnerability | Prevention Strategy |
|---|---|---|
| LocalStorage | XSS (Cross-Site Scripting): Malicious JS scripts can read localStorage data. | Strict input sanitization, strong Content Security Policy (CSP). |
| HttpOnly Cookie | CSRF (Cross-Site Request Forgery): Browser automatically appends cookies to requests. | Use SameSite=Strict or Lax cookie flags, implement anti-CSRF tokens. |
🧪 JWT Token Rotation Flow
Client requests /dashboard (with expired Access Token in memory)
↓
API rejects with 401 Unauthorized
↓
Client calls /api/refresh (Refresh Token automatically sent from HttpOnly Cookie)
↓
Auth Server verifies Refresh Token → issues new short-lived Access Token
↓
Client retries /dashboard with new Access Token
⚡ One-line Interview Answer
JWT auth is best secured by storing short-lived access tokens in application memory and long-lived refresh tokens in secure HttpOnly cookies, protecting the app from XSS and CSRF attacks.
🧠 Simple Definition
Session-based authentication stores user state on the server, while token-based authentication stores user identity inside a signed token (usually JWT) on the client side.
⚡ Super Simple Line
Session = server remembers you
Token = client carries proof of login
🧪 1. Session-Based Auth
🟢 Flow
User logs in
→ Server creates session
→ Stores session in memory/db
→ Sends session ID cookie to client
🧠 On each request:
Client sends session ID
→ Server checks session store
→ Allows access
🧪 Example
Cookie: sessionId=abc123
🧠 Key Idea
Server remembers user state
🧪 2. Token-Based Auth (JWT)
🟢 Flow
User logs in
→ Server creates JWT
→ Sends token to client
→ Client stores token
🧠 On each request:
Client sends JWT
→ Server verifies signature
→ Extracts user data
→ Allows access
🧪 Example
Authorization: Bearer <JWT>
🌍 Real Mental Model
🟢 Session-based
Server = memory bank (remembers users)
🔵 Token-based
Client = carries ID card (token)
Server = just verifies it
🧠 Key Differences Table
FeatureSession-basedToken-based (JWT)StorageServerClientStateStatefulStatelessScalabilityHarderEasierLogoutEasy (server delete session)Hard (token expiry/blacklist)PerformanceDB lookupNo DB lookup
🌍 Real-World Use
🟢 Session-based
Traditional websites
Banking systems
Secure internal apps
🔵 Token-based
REST APIs
Mobile apps
Microservices
SPAs (React apps)
🔥 Important Insight
Session-based = server memory usage
Token-based = client-side responsibility
⚡ One-line Interview Answer
Session-based authentication stores user state on the server and uses a session ID stored in cookies, while token-based authentication stores a signed token (like JWT) on the client, which is sent with each request and verified by the server without storing session state.
🚀 10-Second Version
Session-based auth keeps user data on the server, while token-based auth stores a signed token on the client and verifies it on each request.
🧠 Memory Trick
Session = server remembers
Token = client carries proof
🧠 Simple Definition
Rate limiting in an Express API is a technique to control how many requests a user can make in a given time period to prevent abuse, overload, or DDoS attacks.
⚡ Super Simple Line
Rate limiting = “limit how many requests a user can make in time”
🌍 Why it is needed?
Without rate limiting:
User/attacker → sends 10,000 requests/sec → server crashes
With rate limiting:
User → allowed 100 requests/min → extra requests blocked
🧪 1. Simple Express Rate Limiting (Using library)
Install:
npm install express-rate-limit
Setup:
const express = require("express");
const rateLimit = require("express-rate-limit");
const app = express();
Create limiter:
const limiter = rateLimit({
windowMs: 1 * 60 * 1000, // 1 minute
max: 100, // limit each IP to 100 requests per window
message: "Too many requests, try again later."
});
Apply globally:
app.use(limiter);
🧠 What happens?
Each IP is tracked
Requests are counted per time window
If limit exceeded → request is blocked
🌍 2. Apply to specific routes
app.post("/login", limiter, (req, res) => {
res.send("Login route");
});
👉 Useful for:
login
signup
password reset
🧪 3. Custom Rate Limiter (Interview level)
const requests = new Map();
function rateLimiter(req, res, next) {
const ip = req.ip;
const now = Date.now();
const userData = requests.get(ip) || { count: 0, start: now };
if (now - userData.start > 60000) {
userData.count = 0;
userData.start = now;
}
userData.count++;
requests.set(ip, userData);
if (userData.count > 100) {
return res.status(429).send("Too many requests");
}
next();
}
🧠 Key Idea
Track user requests using IP + time window + count
🌍 Real Production Setup
Usually combined with:
Redis (for distributed systems)
Nginx rate limiting
API gateways (Kong, AWS API Gateway)
🔥 Advanced Strategy (important in interviews)
Different limits per route:
/login → 5 requests/min
/api → 100 requests/min
/admin → stricter rules
⚡ One-line Interview Answer
Rate limiting in Express is implemented by tracking user requests over a time window (usually by IP) and restricting the number of allowed requests using middleware like express-rate-limit or custom logic to prevent abuse and server overload.
🚀 10-Second Version
Rate limiting controls how many requests a user can make in a time window, usually implemented in Express using middleware that tracks IP and request counts.
🧠 Memory Trick
“Count requests → compare limit → allow or block”
🧠 Simple Definition
GraphQL is a query language for APIs that allows clients to request exactly the data they need from a single endpoint, instead of multiple fixed endpoints like REST.
⚡ Super Simple Line
GraphQL = “ask exactly what you want in one request”
REST = “fixed endpoints return fixed data”
🌍 Core Idea
In REST:
/users → returns full user data
/users/1/posts → separate request
In GraphQL:
one endpoint → ask everything you need
🧪 GraphQL Example
Request:
{
user(id: 1) {
name
email
}
}
Response:
{
"data": {
"user": {
"name": "A",
"email": "a@mail.com"
}
}
}
🧠 What is happening?
Client decides what data it needs, not the server.
🔥 GraphQL vs REST
🟢 1. Data Fetching
REST:
Multiple endpoints → multiple requests
GraphQL:
Single endpoint → one request
🟡 2. Over-fetching / Under-fetching
REST problem:
/users returns:
name + email + address + phone + ... (too much)
👉 Over-fetching
Or:
Need extra data:
/users + /users/1/posts (multiple calls)
👉 Under-fetching
GraphQL solves both:
Ask only needed fields
🟣 3. Flexibility
FeatureRESTGraphQLData shapefixedflexibleEndpoint countmultiplesingleClient controllowhigh
🌍 Real-World Example
E-commerce app
REST:
/products
/cart
/user
/orders
GraphQL:
one endpoint → query everything together
🧠 When to use GraphQL?
🟢 Good for:
Complex frontend apps (React, mobile apps)
Multiple data sources
Reducing API calls
Dynamic UI needs
🔴 Not ideal for:
Simple CRUD APIs
Small projects
Caching-heavy systems (harder with GraphQL)
File uploads / streaming-heavy systems
🔥 Key Trade-offs
Pros:
No over-fetching
One request for multiple resources
Strongly typed schema
Great for frontend flexibility
Cons:
More complex setup
Caching is harder
Learning curve
Backend complexity increases
⚡ One-line Interview Answer
GraphQL is an API query language that allows clients to request exactly the data they need from a single endpoint, unlike REST which uses multiple endpoints with fixed responses. It is preferred for complex applications where flexible and efficient data fetching is required.
🚀 10-Second Version
GraphQL lets clients request specific data from a single endpoint, while REST uses multiple endpoints with fixed responses. GraphQL is useful when you need flexible and efficient data fetching.
🧠 Memory Trick
REST = many doors (fixed rooms)
GraphQL = one door (you choose what to take)
🧠 Simple Definition
The N+1 problem in GraphQL happens when resolving nested data causes one query for the parent and then N additional queries for each child item, leading to performance issues.
⚡ Super Simple Line
N+1 problem = 1 query for main data + N extra queries for related data
🌍 Simple Mental Model
1 request → get users
+
N requests → get each user's posts
🧪 Example Scenario
Query:
{
users {
id
name
posts {
title
}
}
}
🧠 What happens (bad case)?
Step 1:
Fetch all users → 1 query
Step 2:
For each user → fetch posts → N queries
Total:
1 + N queries ❌ (bad performance)
🌍 Real Example
If you have:
10 users
Then:
1 query for users
10 queries for posts
= 11 DB queries ❌
🧠 Why it happens?
Because GraphQL resolvers work independently:
User.posts resolver runs for each user separately
🚨 Problem Impact
Slow API responses
Database overload
Scalability issues
High latency
🛠️ How to solve N+1 problem
🟢 1. DataLoader (BEST SOLUTION)
Batches multiple requests into one query
Example:
const DataLoader = require("dataloader");
const userLoader = new DataLoader(async (userIds) => {
const users = await db.users.findMany({
where: { id: { in: userIds } }
});
return userIds.map(id => users.find(u => u.id === id));
});
Result:
Instead of 10 queries → 1 batch query
🟡 2. Join / Eager Loading (SQL level)
SELECT users.*, posts.*
FROM users
LEFT JOIN posts ON posts.userId = users.id;
🔵 3. Batch resolvers
Instead of:
resolver per user
Do:
resolver for all users at once
🟣 4. ORM optimizations
Using tools like:
Prisma
Sequelize
TypeORM
include: { posts: true }
🌍 Best Industry Practice
👉 Most GraphQL systems use:
DataLoader
Batching
Caching layer
🧠 Key Insight
The problem is not GraphQL itself — it is how resolvers are written.
⚡ One-line Interview Answer
The N+1 problem in GraphQL occurs when a single query triggers one database call for the parent and multiple additional calls for each child, leading to performance issues, and it is typically solved using batching techniques like DataLoader or eager loading with joins.
🚀 10-Second Version
N+1 problem happens when GraphQL makes one query for parent data and N queries for related data, and it is solved using batching tools like DataLoader or SQL joins.
🧠 Memory Trick
“1 parent query + many child queries = N+1 problem”
🧠 Simple Definition
NestJS module system is a way to organize an application into feature-based modules, and dependency injection is a pattern where NestJS automatically provides required services (dependencies) to classes instead of creating them manually.
⚡ Super Simple Line
Modules = organize code
Dependency Injection = Nest gives you what you need automatically
🧩 1. What is a Module?
A module is a container that groups related controllers, services, and providers.
Example:
@Module({
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
🧠 Why modules exist?
Organize code by feature
Improve scalability
Separate responsibilities
🌍 Real Mental Model
App
├── UserModule
├── AuthModule
├── ProductModule
🧠 2. What is Dependency Injection (DI)?
Instead of creating objects manually, NestJS automatically injects dependencies into classes.
🧪 Without DI (bad approach)
const service = new UserService();
❌ Hard to manage, tightly coupled
🟢 With DI (NestJS way)
@Injectable()
export class UserService {}
@Controller()
export class UserController {
constructor(private userService: UserService) {}
}
🧠 What is happening?
NestJS sees
UserServiceIt creates instance automatically
Injects it into controller
🌍 Mental Model
Nest Container (IoC)
↓
creates UserService
↓
injects into UserController
🔥 Why DI is powerful?
No manual object creation
Easy testing (mock services)
Loose coupling
Better scalability
🧪 3. Provider system (core of DI)
@Module({
providers: [UserService]
})
👉 Anything in providers can be injected
🧠 How Nest resolves dependencies
Controller → needs Service
↓
Nest checks module providers
↓
creates instance
↓
injects automatically
🧪 Example Flow
@Controller()
export class AppController {
constructor(private service: AppService) {}
}
👉 Nest does:
Create AppService
Pass it into AppController
🟡 Advanced Insight (Interview gold)
NestJS uses an IoC (Inversion of Control) container to manage dependency injection.
🌍 Real-World Analogy
You don't cook ingredients yourself
Chef (Nest) gives you ready ingredients (services)
🧠 Key Differences
ConceptMeaningModuleGroups related featuresProviderInjectable serviceDIAutomatic dependency management
⚡ One-line Interview Answer
NestJS module system organizes application features into logical modules, while dependency injection allows Nest to automatically create and inject required services into controllers and other providers using its IoC container.
🚀 10-Second Version
Modules organize code into features, and dependency injection lets NestJS automatically provide required services instead of manually creating them.
🧠 Memory Trick
Module = structure
DI = automatic supply of dependencies
🧠 Simple Definition
A background job queue is a system that runs heavy or delayed tasks outside the main request/response flow. Bull / BullMQ are Node.js libraries that manage these queues using Redis.
⚡ Super Simple Line
Job queue = “do heavy work later in background without blocking API”
🌍 Why we need it?
Without queue:
User request → send email → wait → slow API ❌
With queue:
User request → add job → respond instantly ✅
Worker sends email in background
🧪 Example Use Cases
Sending emails
Image processing
Video encoding
Payment processing
Notifications
Report generation
🧠 What is Bull / BullMQ?
Bull and BullMQ are Redis-based queue libraries for Node.js used to manage background jobs reliably.
🔥 Key Idea
Producer → adds job
Redis → stores queue
Worker → processes job
🧪 Simple BullMQ Example
🟢 1. Install
npm install bullmq ioredis
🔵 2. Create Queue (Producer)
const { Queue } = require("bullmq");
const emailQueue = new Queue("email-queue", {
connection: { host: "localhost", port: 6379 }
});
async function addJob() {
await emailQueue.add("sendEmail", {
to: "user@mail.com",
subject: "Welcome!"
});
}
🟡 3. Worker (Consumer)
const { Worker } = require("bullmq");
new Worker("email-queue", async job => {
console.log("Sending email to:", job.data.to);
});
🧠 What is happening?
API → adds job → Redis stores → Worker picks → executes
🌍 Real Mental Model
API Server → fast response
↓
Redis Queue
↓
Worker Server (background tasks)
🔥 Why Redis?
Fast in-memory storage
Reliable queue persistence
Supports retries & delays
🧪 Bull/BullMQ Features
🟢 Job retries
{
attempts: 3
}
🟡 Delayed jobs
delay: 60000 // run after 1 minute
🔵 Rate limiting
limiter: {
max: 10,
duration: 1000
}
🟣 Job failure handling
automatic retry
error logging
🧠 Bull vs BullMQ
FeatureBullBullMQArchitectureoldermodernPerformancegoodbetterTypeScriptlimitedfull supportMaintenanceslower updatesactively maintained
🌍 Real Production Architecture
API Server → Queue → Redis → Worker Cluster
🧠 Key Insight
API should never do heavy work directly — it should delegate to background workers.
⚡ One-line Interview Answer
A background job queue is used to offload heavy or time-consuming tasks to separate workers. Bull/BullMQ are Redis-based Node.js libraries that manage job queues, enabling producers to add jobs and workers to process them asynchronously.
🚀 10-Second Version
Job queues run heavy tasks in the background. Bull/BullMQ use Redis to store jobs and workers to process them asynchronously.
🧠 Memory Trick
API = add job
Redis = store job
Worker = do job
🧠 Simple Definition
SQL Injection is a security vulnerability where an attacker inserts malicious SQL code into user input, causing the database to execute unintended queries.
⚡ Super Simple Line
SQL Injection = user input becomes part of SQL query
🌍 Why is it dangerous?
An attacker may:
Read sensitive data
Delete data
Modify data
Bypass login systems
🧪 Vulnerable Example
const query = `
SELECT * FROM users
WHERE email = '${email}'
`;
User enters:
' OR '1'='1
Query becomes:
SELECT * FROM users
WHERE email = '' OR '1'='1'
Result:
Condition always true
👉 Attacker may get access to all users.
🧪 Login Bypass Example
Original query:
SELECT * FROM users
WHERE username='admin'
AND password='123'
Malicious input:
admin' --
Query becomes:
SELECT * FROM users
WHERE username='admin' --'
AND password='123'
👉 Password check is ignored.
🛡️ How to Prevent SQL Injection?
🟢 1. Use Parameterized Queries (Best Practice)
❌ Bad
const query =
`SELECT * FROM users WHERE id = ${id}`;
✅ Good
db.query(
"SELECT * FROM users WHERE id = ?",
[id]
);
🧠 Why?
Database treats:
id
as data, not SQL code.
🟡 2. Use ORM / Query Builder
Examples:
Prisma
TypeORM
Sequelize
await prisma.user.findUnique({
where: { id }
});
👉 ORM automatically handles parameterization.
🔵 3. Validate User Input
if (!Number.isInteger(id)) {
throw new Error("Invalid ID");
}
🟣 4. Principle of Least Privilege
Database user should have only necessary permissions.
Example:
Read-only user
instead of:
Full database admin
🔴 5. Never Build Queries with String Concatenation
❌ Dangerous
const sql =
"SELECT * FROM users WHERE name='" + name + "'";
🌍 Real-World Example
Safe PostgreSQL query
const result = await pool.query(
"SELECT * FROM users WHERE email = $1",
[email]
);
🧠 Key Insight
The safest approach is parameterized queries. Never trust user input.
⚡ One-line Interview Answer
SQL Injection is an attack where malicious SQL code is inserted through user input and executed by the database. It is prevented by using parameterized queries, ORMs, input validation, and avoiding string concatenation when building SQL statements.
🚀 10-Second Version
SQL Injection happens when user input is treated as SQL code. Prevent it using parameterized queries, ORMs, and proper input validation.
🧠 Memory Trick
User input + SQL string = danger
Parameterized query = safe
Interview Gold Sentence
"I never concatenate user input into SQL queries. I always use parameterized queries or an ORM like Prisma to ensure user input is treated as data rather than executable SQL."
🧠 Simple Definition
XSS (Cross-Site Scripting)
XSS happens when an attacker injects malicious JavaScript into a webpage, and that script executes in another user's browser.
CSRF (Cross-Site Request Forgery)
CSRF happens when an attacker tricks a logged-in user into sending unwanted requests to a website without their knowledge.
⚡ Super Simple Line
XSS = attacker injects JavaScript
CSRF = attacker tricks user into sending requests
🧪 XSS Example
Imagine a comment system:
<script>
alert("Hacked!");
</script>
If the application displays it directly:
div.innerHTML = userComment;
The script executes.
Result
Attacker's JavaScript runs
Possible damage:
Steal JWT tokens
Steal user data
Modify page content
Act as the user
🛡️ How to Prevent XSS
🟢 Escape User Input
❌ Dangerous
element.innerHTML = comment;
✅ Safer
element.textContent = comment;
🟡 Sanitize HTML
Use libraries that remove dangerous tags/scripts.
🔵 Content Security Policy (CSP)
Only allow trusted scripts
🟣 Avoid Storing Tokens in localStorage
Because XSS can read:
localStorage.getItem("token")
Prefer:
HTTP-only cookies
🧪 CSRF Example
User is logged into:
bank.com
Attacker creates:
<img src="https://bank.com/transfer?amount=1000">
User visits attacker's site.
Browser automatically sends:
bank.com cookies
Bank thinks:
Request came from authenticated user
and processes it.
🛡️ How to Prevent CSRF
🟢 CSRF Tokens
Server generates:
random secret token
Frontend sends it back:
X-CSRF-Token: abc123
Attacker cannot know this token.
🟡 SameSite Cookies
SameSite=Strict
or
SameSite=Lax
Prevents cookies from being sent on many cross-site requests.
🔵 Verify Origin / Referer Header
Only accept requests from:
https://myapp.com
🌍 Key Difference
FeatureXSSCSRFGoalRun malicious JSSend fake requestsRequires user loginNot alwaysUsually yesAttack locationVictim websiteExternal websiteMain defenseSanitization + CSPCSRF token + SameSite cookies
🧠 Easy Memory Trick
XSS
Attacker injects script
Think:
XSS = Execute Script
CSRF
Attacker forges request
Think:
CSRF = Fake Request
⚡ One-line Interview Answer
XSS occurs when malicious JavaScript is injected and executed in a user's browser, while CSRF tricks authenticated users into sending unintended requests. XSS is prevented through input sanitization, escaping, CSP, and HTTP-only cookies, while CSRF is prevented using CSRF tokens, SameSite cookies, and origin validation.
🚀 10-Second Version
XSS injects malicious scripts into a webpage, while CSRF tricks logged-in users into making requests. Prevent XSS with sanitization and CSP, and prevent CSRF with CSRF tokens and SameSite cookies.
🧠 Interview Gold Sentence
"For XSS, I sanitize user input and use HTTP-only cookies. For CSRF, I use CSRF tokens and SameSite cookie settings to ensure requests are coming from trusted sources."
🧠 Simple Definition
Passwords should never be stored in plain text. They should be hashed using a strong one-way hashing algorithm like bcrypt or Argon2 before being saved in the database.
⚡ Super Simple Line
Never store passwords directly. Store a secure hash of the password.
🌍 Why not store plain text?
❌ Bad
Database:
email password
-------------------------
user@mail.com 123456
If database is leaked:
👉 Every password is exposed.
✅ Good
Database:
email password
-------------------------
user@mail.com $2b$10$...
Even if database leaks:
👉 Actual password is not visible.
🧠 What is Hashing?
Hashing converts a password into a fixed string that cannot realistically be reversed.
Example:
password123
becomes
$2b$10$X7K...
🛠️ How to Store Passwords Securely
🟢 Use bcrypt
Install:
npm install bcrypt
Hash password before saving:
const bcrypt = require("bcrypt");
const hashedPassword =
await bcrypt.hash(password, 10);
Save:
Database → hashedPassword
🧪 Login Verification
User logs in:
password123
Compare with stored hash:
const isValid =
await bcrypt.compare(
password,
storedHash
);
🧠 What is Salt?
A salt is random data added before hashing.
password123
+
random salt
↓
unique hash
This prevents:
Rainbow table attacks
Good news
bcrypt automatically handles salt.
🔥 Recommended Algorithms
✅ Good
bcrypt
Argon2 (modern favorite)
scrypt
❌ Avoid
MD5
SHA1
Too fast and insecure for passwords.
🌍 Real Authentication Flow
User enters password
↓
Hash password
↓
Store hash in DB
↓
Login
↓
Compare password with hash
↓
Allow / deny
🚨 Common Interview Mistakes
❌ Encrypt passwords
Encryption can be decrypted
Passwords should be:
Hashed, not encrypted
❌ Store plain text
Never do:
password: "123456"
⚡ One-line Interview Answer
Passwords should be securely stored using a one-way hashing algorithm such as bcrypt or Argon2 with salting. During login, the provided password is hashed and compared against the stored hash rather than storing or comparing plain-text passwords.
🚀 10-Second Version
I never store plain-text passwords. I hash them using bcrypt or Argon2 with salt and verify them using secure hash comparison during login.
🧠 Memory Trick
Password → Hash → Store
Login → Compare → Access
🎯 Interview Gold Sentence
"I use bcrypt or Argon2 to hash passwords with salt before storing them. Since hashing is one-way, even if the database is compromised, attackers cannot directly recover the original passwords."
🧠 Simple Definition
Pagination is how we split large datasets into smaller chunks. Offset pagination skips a number of records, while cursor pagination uses a reference point (cursor) to fetch the next set of results.
⚡ Super Simple Line
Offset = “skip N items”
Cursor = “continue from last item”
🧪 1. Offset Pagination
Example:
/users?limit=10&offset=20
👉 Meaning:
skip first 20 users
return next 10 users
SQL version:
SELECT * FROM users
LIMIT 10 OFFSET 20;
🧠 How it works
Page 1 → 0–10
Page 2 → 10–20
Page 3 → 20–30
🟢 Pros of Offset Pagination
Simple to implement
Easy to understand
Good for small datasets
🔴 Cons of Offset Pagination
❌ Slow for large datasets
OFFSET 100000 → database still scans rows
❌ Inconsistent data
If new records are added:
pages shift → duplicates or missing items
🧪 2. Cursor Pagination
Example:
/users?limit=10&cursor=abc123
Meaning:
👉 “Give me next 10 users after this specific item”
🧠 How it works
Instead of skipping, it continues from a position:
User ID = 50 → next page starts from 51+
🟢 Pros of Cursor Pagination
Very fast (no skipping)
Works well with large datasets
Consistent results even if data changes
🔴 Cons of Cursor Pagination
More complex to implement
Harder to debug manually
Cannot easily jump to page 10
🌍 Real Mental Model
Offset
Book → go to page 50 directly
But book must flip through pages internally.
Cursor
Bookmark → continue from where you left
🔥 Key Differences Table
FeatureOffsetCursorMechanismSkip N rowsContinue from pointerPerformanceSlow on large dataFastConsistencyCan breakStableComplexityEasyMediumUse caseSmall appsLarge-scale systems
🧪 Real-World Example
Offset:
/posts?page=5
👉 page shifting issue possible
Cursor:
/posts?cursor=lastPostId123
👉 always reliable ordering
🧠 When to use what?
🟢 Use Offset when:
small dataset
admin panels
simple apps
🔵 Use Cursor when:
large datasets
social media feeds
infinite scroll
real-time apps
⚡ One-line Interview Answer
Offset pagination skips a fixed number of records using page and offset values, while cursor pagination uses a reference to the last item fetched, making it more efficient and consistent for large or frequently changing datasets.
🚀 10-Second Version
Offset pagination skips records using page numbers, while cursor pagination continues from the last item, making it faster and more reliable for large datasets.
🧠 Memory Trick
Offset = skip
Cursor = continue
🎯 Interview Gold Sentence
"Offset pagination is simpler but inefficient for large datasets, while cursor pagination is preferred in scalable systems because it avoids skipping rows and provides consistent results even when data changes."
🧠 Simple Definition
An idempotent API or webhook handler is designed so that repeating the same request multiple times produces the same result as doing it once, without causing duplicate side effects.
⚡ Super Simple Line
Idempotent = “same request → same result every time”
🌍 Why it matters?
Because in real systems:
Requests can be retried automatically
Network can fail
Webhooks can be sent multiple times
👉 Without idempotency → duplicates happen
🧪 Problem Example (NOT idempotent)
Payment API:
POST /pay → deducts $100
If request is sent twice:
User gets charged $200 ❌
🧠 What is the goal?
Same request repeated → no extra side effect
🛠️ How to design Idempotent APIs
🟢 1. Use Idempotency Keys (BEST PRACTICE)
Client sends:
Idempotency-Key: abc123
Server logic:
If key exists → return previous response
If not → process request + store result
Result:
Same request repeated → no duplicate action
🧪 Example (Payment API)
if (db.exists(idempotencyKey)) {
return db.getResponse(idempotencyKey);
}
const result = processPayment();
db.save(idempotencyKey, result);
🟡 2. Use Unique Constraints in DB
orderId must be unique
👉 Prevents duplicate inserts
Example:
CREATE UNIQUE INDEX ON payments(transaction_id);
🟣 3. Use PUT instead of POST (when appropriate)
MethodIdempotentGETYesPUTYesDELETEYesPOSTNo
Example:
PUT /user/1
👉 Always updates same user state
🟠 4. Deduplication in Webhooks
Problem:
Stripe sends webhook twice
Solution:
Store event ID → ignore duplicates
Example:
if (processedEvents.has(event.id)) return;
processedEvents.add(event.id);
processEvent(event);
🧠 Real Mental Model
Request → check uniqueness → process once → store result
🌍 Real-World Example
Payment system:
User clicks “Pay”
Network retries request
Server checks idempotency key
Prevents double charge
🔥 Where idempotency is critical
Payments
Order creation
Webhooks
Email sending
Payment gateways (Stripe, PayPal)
🚨 Common mistake
❌ Not handling retries
Retry → duplicate transaction
⚡ One-line Interview Answer
Idempotent APIs ensure that multiple identical requests produce the same result without causing duplicate side effects, typically implemented using idempotency keys, database constraints, and careful handling of HTTP methods like PUT and DELETE.
🚀 10-Second Version
Idempotent APIs ensure repeated requests don’t create duplicates, using techniques like idempotency keys, unique database constraints, and proper HTTP methods.
🧠 Memory Trick
“Same request → same result → no duplication”
🎯 Interview Gold Sentence
"I design idempotent APIs using idempotency keys for external requests like payments or webhooks, and enforce uniqueness at the database level to ensure retries or duplicate calls do not create inconsistent or duplicate state."
🧠 Simple Definition
Preventing duplicate background job processing means ensuring the same job is not executed more than once, even if it is enqueued or retried multiple times.
⚡ Super Simple Line
No matter how many times a job is queued → it should run only once
🌍 Why duplicates happen?
Retries
Network failures
Worker crashes
Webhook repeats
Multiple producers
👉 All can enqueue the same job multiple times
🧪 Problem Example
SendEmail(user123)
SendEmail(user123)
👉 User gets 2 emails ❌
🛠️ How to prevent duplicate job processing
🟢 1. Use Job Idempotency Key (BEST METHOD)
Example:
jobId = "send-email-user123"
Bull/BullMQ:
queue.add("sendEmail", data, {
jobId: "send-email-user123"
});
Result:
Same jobId → ignored or replaced
🧠 Key Idea
jobId acts as a fingerprint
🟡 2. Deduplication in Redis / DB
Store processed jobs:
SET processed_jobs jobId
Check before processing:
if (await redis.exists(jobId)) return;
await redis.set(jobId, true);
processJob();
🟣 3. Make Job Handler Idempotent
Even if duplicate runs happen:
Check before action → avoid duplicate side effects
Example:
if (order.status === "paid") return;
🔵 4. Unique Constraints in Database
CREATE UNIQUE INDEX ON emails(user_id, type);
👉 Prevents duplicate writes
🟠 5. Locking (Distributed Lock)
Use Redis lock:
SET lock:job123 NX EX 60
👉 Only one worker processes job
🌍 Real Mental Model
Queue → Check uniqueness → Lock → Process → Mark done
🔥 Real-World Example
Email system:
Job queued twice
Worker checks jobId
Redis says “already processed”
Second execution skipped
🚨 Common mistake
❌ Assuming queue prevents duplicates
Queue ≠ guaranteed single execution
⚡ One-line Interview Answer
Duplicate background job processing is prevented using idempotency keys (jobId), deduplication in Redis or database, distributed locks, and making job handlers idempotent so repeated executions do not produce duplicate side effects.
🚀 10-Second Version
We prevent duplicate jobs using unique job IDs, Redis/DB deduplication, distributed locks, and idempotent job handlers.
🧠 Memory Trick
“Same job → same ID → run once only”
🎯 Interview Gold Sentence
"I prevent duplicate job execution by assigning unique job IDs in the queue, using Redis or database checks to deduplicate processing, and ensuring job handlers are idempotent so retries or crashes do not cause duplicate side effects."
🧠 Simple Definition (Word-for-word)
A Message Queue is a point-to-point communication channel where messages are sent by a producer, stored sequentially, and consumed by a single consumer (e.g. BullMQ, simple queues). A Message Broker is a middleware system designed to translate, route, and distribute messages from producers to multiple consumers using complex routing mechanisms (such as pub/sub exchanges or distributed event logs like Kafka and RabbitMQ).
⚡ Super Simple Line
Message Queue = 1-to-1 processing (one sender sends a job to exactly one worker).
Message Broker = 1-to-many routing (one event is published to multiple interested services).
📊 Key Differences Table
| Feature | Message Queue | Message Broker |
|---|---|---|
| Interaction Pattern | Point-to-point (One-to-One) | Publish/Subscribe (One-to-Many) |
| Message Routing | None (direct FIFO queue) | Complex routing via exchanges, headers, topics |
| Consumer Model | Competing consumers (first worker takes it) | Broadcast/Multicast (all subscribers get a copy) |
| Example Technologies | BullMQ, Amazon SQS, Redis Lists | RabbitMQ, Apache Kafka, Google Pub/Sub |
🧪 Real-World Use Case
Message Queue (BullMQ): A user uploads an image. Your backend adds a single job to the queue, and one worker process consumes it to compress the image.
Message Broker (Kafka/RabbitMQ): An order is completed. An event
OrderPlacedis published to the broker. The Invoice Service, Shipping Service, and Notification Service all receive a copy of this message to run parallel tasks.
⚡ One-line Interview Answer
Message queues facilitate point-to-point delivery to a single consumer, while message brokers support complex routing and pub/sub patterns to distribute events to multiple distinct consumer services.
🧠 Simple Definition
A message broker is a system that enables different services or applications to communicate asynchronously by sending messages through a central intermediary instead of calling each other directly.
⚡ Super Simple Line
Message broker = “middleman that delivers messages between services”
🌍 Core Idea
Service A → sends message → Broker → Service B consumes it
👉 They don’t talk directly
🧪 Simple Example
Without message broker ❌
Order Service → Email Service (direct call)
Order Service waits for email response
👉 tightly coupled, slow, fragile
With message broker ✅
Order Service → Broker → Email Service
👉 async, fast, decoupled
🧠 Why message brokers exist?
decouple services
handle async tasks
improve scalability
prevent system overload
enable retries and reliability
🟢 Popular Message Brokers
RabbitMQ
Apache Kafka
Redis (used for queues too)
AWS SQS (cloud-based queue)
🧪 Real-World Flow Example
E-commerce order system
User places order
→ Order Service
→ Message Broker
→ Email Service
→ Notification Service
→ Inventory Service
👉 everything runs independently
🌍 Mental Model
Producer → Broker → Consumer
Producer = sends message
Broker = stores & routes message
Consumer = processes message
🔥 What message brokers actually do
🟢 1. Message storage
Messages are queued safely
🟡 2. Delivery guarantee
retries failed messages
ensures reliability
🔵 3. Load balancing
Multiple workers consume messages
🟣 4. Async processing
API responds immediately, work happens later
🧪 Example (Node.js concept)
// producer
queue.send("sendEmail", { userId: 1 });
// consumer
queue.process(async (job) => {
sendEmail(job.data.userId);
});
🧠 Key Insight
Message brokers turn synchronous communication into asynchronous event-driven architecture.
🔥 Why not direct API calls?
❌ Problems:
tight coupling
slower response
failure propagation
scaling issues
✅ With broker:
independent services
retry support
scalable consumers
⚡ One-line Interview Answer
A message broker is an intermediary system that enables asynchronous communication between services by receiving, storing, and delivering messages, allowing systems to be decoupled, scalable, and resilient. Common examples include RabbitMQ, Kafka, and Redis.
🚀 10-Second Version
A message broker is a system that allows services to communicate asynchronously by sending messages through a central queue instead of direct calls.
🧠 Memory Trick
Producer → Broker → Consumer
(send → store → process)
🎯 Interview Gold Sentence
"I use message brokers like RabbitMQ or Kafka to decouple services and enable asynchronous communication, which improves scalability, fault tolerance, and system performance by offloading heavy tasks from the main API flow."
🧠 Simple Definition
EventEmitter in Node.js is a core module that allows objects to communicate through events by emitting signals and listening for them using handlers.
⚡ Super Simple Line
EventEmitter = “one part sends event, another part reacts to it”
🌍 Core Idea
Emitter → emits event → Listener → reacts to event
🧪 Simple Example
const EventEmitter = require("events");
const emitter = new EventEmitter();
Register listener
emitter.on("greet", () => {
console.log("Hello received!");
});
Emit event
emitter.emit("greet");
Output
Hello received!
🧠 What is happening?
.on()= listen for event.emit()= trigger event
🌍 Real Mental Model
Doorbell system
Press button → ring event → people react
🧪 Real-World Example
Order system:
emitter.on("orderPlaced", sendEmail);
emitter.on("orderPlaced", updateInventory);
Trigger:
emitter.emit("orderPlaced", orderData);
👉 Multiple listeners react to same event
🔥 Key Methods
🟢 .on()
Listen to event (can run many times)
🟡 .once()
Listen only once
🔵 .emit()
Trigger event
🟣 .removeListener()
Stop listening
🌍 Why EventEmitter exists?
Node.js is event-driven:
Async operations → trigger events → handlers respond
🧪 Example in Node.js internals
HTTP server emits request event
Streams emit data events
File system emits completion events
🧠 Mental Model
Event bus inside Node.js
🔥 Why it is powerful
Decouples code
Easy communication between modules
Supports async workflows
Used in core Node.js APIs
🚨 Common mistake
❌ Thinking it's only for logging
EventEmitter = just logs ❌
👉 It is a core architecture pattern in Node.js
🧪 Advanced Insight
You can pass data:
emitter.emit("userCreated", { id: 1 });
Listener:
emitter.on("userCreated", (user) => {
console.log(user.id);
});
⚡ One-line Interview Answer
EventEmitter in Node.js is a core module that implements the publish-subscribe pattern, allowing objects to emit named events and other parts of the application to listen and react to those events asynchronously.
🚀 10-Second Version
EventEmitter lets parts of a Node.js app communicate by emitting events and listening to them using handlers in an event-driven way.
🧠 Memory Trick
emit = send event
on = listen event
🎯 Interview Gold Sentence
"EventEmitter in Node.js is a core event-driven pattern where one part of the system emits events and other parts listen and react, enabling loose coupling and asynchronous communication across the application."