Interview Prep
Interview Questions Node.js — Event Loop, Async Patterns, and What Startups Actually Ask
Node.js powers the backend of most Indian startups. The interview is not about knowing Express.js syntax — it is about understanding the event loop, async patterns, and how to build APIs that do not block under load.

Node.js is the default backend for Indian startups — understanding the event loop is non-negotiable.
The Node.js Interview Landscape
Node.js is the default backend for Indian startups — Razorpay, CRED, and Swiggy's internal tools all run on it. Service companies use it less (they prefer Java), but product companies and startups test it heavily. The interview focuses on event loop understanding, async patterns, and practical API building.
Unlike Java interviews that test language features, Node.js interviews test your understanding of how the runtime works. The event loop, non-blocking I/O, and async patterns are not just theory questions — they determine whether you can build applications that scale under real traffic.
This guide covers the actual Node.js questions asked in Indian interviews — organized by topic, with code examples and the depth interviewers expect. Not documentation summaries, but the practical understanding that separates developers who use Node from developers who understand it.
If you cannot explain the event loop in 60 seconds, you are not ready for a Node.js interview. It is the single question that separates developers who use Node from developers who understand it.
Event Loop Questions
The event loop is the heart of Node.js. These three questions eliminate 50% of candidates — not because they are hard, but because most developers never go beyond “Node.js is single-threaded and non-blocking.”
Q1: Explain the Node.js event loop
Why they ask: This is the single most important Node.js interview question. It tests whether you understand the runtime architecture — single-threaded event loop with non-blocking I/O and event-driven callbacks. This question eliminates 50% of candidates.
What the interviewer wants: Not just “single-threaded and non-blocking” — they want you to explain the phases and how the event loop processes different types of callbacks.
// The Node.js Event Loop Phases:
// 1. Timers → executes setTimeout() and setInterval() callbacks
// 2. Pending → executes I/O callbacks deferred to the next loop
// 3. Idle/Prepare → internal use only
// 4. Poll → retrieves new I/O events, executes I/O callbacks
// 5. Check → executes setImmediate() callbacks
// 6. Close → executes close event callbacks (socket.on('close'))
// Example showing execution order:
console.log('1 - Start');
setTimeout(() => console.log('2 - setTimeout'), 0);
setImmediate(() => console.log('3 - setImmediate'));
process.nextTick(() => console.log('4 - nextTick'));
Promise.resolve().then(() => console.log('5 - Promise'));
console.log('6 - End');
// Output:
// 1 - Start
// 6 - End
// 4 - nextTick (microtask queue - runs before next phase)
// 5 - Promise (microtask queue - runs after nextTick)
// 2 - setTimeout (timers phase)
// 3 - setImmediate (check phase)Q2: What is the difference between process.nextTick() and setImmediate()?
Why they ask: This tests deep understanding of the event loop microtask queue. Most candidates confuse the two or cannot explain when to use each.
// process.nextTick() — runs BEFORE the next event loop iteration
// It is added to the microtask queue, which is processed between phases
// setImmediate() — runs in the CHECK phase of the event loop
// It is processed after the poll phase completes
// Example:
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick'));
// Output: nextTick, setImmediate
// nextTick always runs first because microtask queue has priority
// WARNING: Recursive process.nextTick() can starve the event loop
function recursiveNextTick() {
process.nextTick(recursiveNextTick); // BAD — blocks I/O forever
}
// setImmediate is safer for recursive operations
function recursiveImmediate() {
setImmediate(recursiveImmediate); // OK — allows I/O between calls
}Q3: Is Node.js single-threaded? How does it handle concurrent requests?
Why they ask: The trap: candidates say “yes, single-threaded” and stop. The full answer involves libuv, the thread pool, and worker threads. Interviewers want the complete picture.
// The event loop is single-threaded — YES
// But Node.js is NOT purely single-threaded
// libuv provides a thread pool (default 4 threads) for:
// - File system operations (fs.readFile, fs.writeFile)
// - DNS lookups (dns.lookup)
// - Crypto operations (crypto.pbkdf2)
// - Compression (zlib)
// Network I/O uses OS-level async mechanisms (epoll, kqueue, IOCP)
// NOT the thread pool — this is why Node.js handles thousands of connections
// For CPU-intensive tasks, use Worker Threads:
const { Worker } = require('worker_threads');
const worker = new Worker('./heavy-computation.js');
worker.on('message', (result) => {
console.log('Result from worker:', result);
});
worker.postMessage({ data: largeDataSet });
// You can increase the thread pool size:
// UV_THREADPOOL_SIZE=8 node app.js (default is 4, max is 1024)Key insight: The event loop handles orchestration (single-threaded), libuv handles I/O (thread pool), and the OS handles network connections (async kernel APIs). Understanding this three-layer architecture is what interviewers are really testing.
Async Patterns
Async programming is what makes Node.js powerful — and what makes it confusing. Interviewers test whether you understand the evolution from callbacks to Promises to async/await, and when to use each pattern.
Q1: Explain callbacks, Promises, and async/await — when to use each
Why they ask: This tests whether you understand the evolution of async patterns in Node.js and can articulate the trade-offs. Interviewers want to see all three approaches for the same operation.
const fs = require('fs');
// 1. Callback pattern (original Node.js way)
fs.readFile('data.json', 'utf8', (err, data) => {
if (err) {
console.error('Error:', err);
return;
}
console.log(data);
});
// Problem: callback hell when nesting multiple async operations
// 2. Promise pattern
const fsPromises = require('fs').promises;
fsPromises.readFile('data.json', 'utf8')
.then(data => console.log(data))
.catch(err => console.error('Error:', err));
// Better: chainable, avoids deep nesting
// 3. Async/Await pattern (cleanest)
async function readData() {
try {
const data = await fsPromises.readFile('data.json', 'utf8');
console.log(data);
} catch (err) {
console.error('Error:', err);
}
}
// Best: reads like synchronous code, easy error handling with try/catch
// When to use each:
// Callbacks: legacy code, event emitters (stream.on('data', callback))
// Promises: when you need Promise.all/race for parallel operations
// Async/Await: default choice for most new codeQ2: What is Promise.all vs Promise.allSettled vs Promise.race?
Why they ask: This tests whether you know the right tool for different concurrency scenarios. Most candidates only know Promise.all — knowing allSettled and race shows depth.
const fetchUser = fetch('/api/user');
const fetchOrders = fetch('/api/orders');
const fetchNotifications = fetch('/api/notifications');
// Promise.all — fails fast on first rejection
// Use when ALL results are required
try {
const [user, orders, notifications] = await Promise.all([
fetchUser, fetchOrders, fetchNotifications
]);
} catch (err) {
// If ANY promise rejects, entire Promise.all rejects
console.error('One request failed:', err);
}
// Promise.allSettled — waits for ALL, returns status of each
// Use when you want results regardless of individual failures
const results = await Promise.allSettled([
fetchUser, fetchOrders, fetchNotifications
]);
results.forEach(result => {
if (result.status === 'fulfilled') console.log(result.value);
if (result.status === 'rejected') console.log(result.reason);
});
// Promise.race — resolves/rejects with the FIRST settled promise
// Use for timeouts or fastest-response-wins scenarios
const result = await Promise.race([
fetch('/api/data'),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 5000)
)
]);Q3: How do you handle errors in async/await?
Why they ask: The most common mistake in Node.js applications is unhandled promise rejections. Interviewers want to know if you handle errors properly in production code.
// Method 1: try/catch blocks
async function getUser(id) {
try {
const user = await UserModel.findById(id);
if (!user) throw new Error('User not found');
return user;
} catch (err) {
console.error('Failed to get user:', err.message);
throw err; // Re-throw for the caller to handle
}
}
// Method 2: Express error-handling middleware
app.get('/api/users/:id', async (req, res, next) => {
try {
const user = await getUser(req.params.id);
res.json(user);
} catch (err) {
next(err); // Pass to error middleware
}
});
// Error-handling middleware (4 parameters — Express recognizes this)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: err.message || 'Internal Server Error'
});
});
// Common mistake: forgetting to catch rejected promises
// This causes UnhandledPromiseRejection warnings/crashes
async function bad() {
const data = await riskyOperation(); // No try/catch = dangerous
}
Async patterns are the backbone of Node.js — mastering them is the difference between building APIs that work and APIs that scale.
Express & REST APIs
Express.js is the most popular Node.js framework and is tested in virtually every Node.js interview. Interviewers want to see that you can build a complete API with routing, middleware, and error handling.
Q1: Build a REST API with Express.js — routing, middleware, error handling
Why they ask: This is the practical test. Interviewers want to see if you can set up an Express app with proper structure — routes, middleware chain, and centralized error handling.
const express = require('express');
const app = express();
// Built-in middleware
app.use(express.json()); // Parse JSON bodies
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded bodies
// Custom logging middleware
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // MUST call next() or request hangs
});
// Routes
app.get('/api/users', async (req, res, next) => {
try {
const users = await User.find();
res.json(users);
} catch (err) {
next(err);
}
});
app.get('/api/users/:id', async (req, res, next) => {
try {
const user = await User.findById(req.params.id); // req.params
if (!user) return res.status(404).json({ error: 'User not found' });
res.json(user);
} catch (err) {
next(err);
}
});
// Query params: /api/users/search?name=John&age=25
app.get('/api/users/search', async (req, res, next) => {
const { name, age } = req.query; // req.query
// ... search logic
});
app.post('/api/users', async (req, res, next) => {
try {
const user = new User(req.body); // req.body
await user.save();
res.status(201).json(user);
} catch (err) {
next(err);
}
});
// Error-handling middleware (must have 4 parameters)
app.use((err, req, res, next) => {
res.status(err.status || 500).json({ error: err.message });
});
app.listen(3000);Q2: What is middleware in Express and how does the middleware chain work?
Why they ask: Middleware is the core concept of Express. Interviewers want to know that you understand the next() function, that order matters, and how to write authentication middleware.
// Middleware = function with (req, res, next)
// Executes in ORDER they are defined — order matters!
// Authentication middleware example
function authenticate(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded; // Attach user to request object
next(); // Pass to next middleware/route handler
} catch (err) {
return res.status(403).json({ error: 'Invalid token' });
}
}
// Apply to specific routes
app.get('/api/profile', authenticate, (req, res) => {
res.json(req.user); // req.user was set by authenticate middleware
});
// Apply to all routes under a path
app.use('/api/admin', authenticate, adminRoutes);
// Middleware chain: request → logger → auth → route handler → error handler
// If any middleware does NOT call next(), the request stops thereDatabase Queries
Indian startups predominantly use MongoDB with Mongoose, while some product companies use PostgreSQL. Interviewers test both your ability to work with databases and your awareness of security concerns like SQL injection.
Q1: How do you connect Node.js to MongoDB using Mongoose?
Why they ask: MongoDB + Mongoose is the most common database stack in Indian startups. Interviewers want to see schema definition, model creation, and CRUD operations.
const mongoose = require('mongoose');
// Connect to MongoDB
mongoose.connect(process.env.MONGODB_URI);
// Schema definition
const userSchema = new mongoose.Schema({
name: { type: String, required: true, trim: true },
email: { type: String, required: true, unique: true, lowercase: true },
age: { type: Number, min: 18 },
role: { type: String, enum: ['user', 'admin'], default: 'user' },
createdAt: { type: Date, default: Date.now }
});
// Model creation
const User = mongoose.model('User', userSchema);
// CRUD operations in controller
async function createUser(req, res) {
const user = new User(req.body);
await user.save();
res.status(201).json(user);
}
async function getUsers(req, res) {
const users = await User.find({ role: 'user' })
.select('name email') // Select specific fields
.sort({ createdAt: -1 }) // Sort by newest first
.limit(10); // Pagination
res.json(users);
}
async function updateUser(req, res) {
const user = await User.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true } // Return updated doc, validate
);
res.json(user);
}
async function deleteUser(req, res) {
await User.findByIdAndDelete(req.params.id);
res.status(204).send();
}Q2: How do you prevent SQL injection in Node.js with PostgreSQL?
Why they ask: Security awareness is critical. Interviewers want to see that you understand parameterized queries and never use string concatenation for SQL queries.
const { Pool } = require('pg');
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
// VULNERABLE — never do this!
async function unsafeQuery(userId) {
// String concatenation = SQL injection risk
const result = await pool.query(
"SELECT * FROM users WHERE id = '" + userId + "'"
);
// If userId = "1' OR '1'='1" → returns ALL users
return result.rows;
}
// SAFE — parameterized query
async function safeQuery(userId) {
const result = await pool.query(
'SELECT * FROM users WHERE id = $1', // $1 is a parameter placeholder
[userId] // Parameters passed separately
);
return result.rows;
}
// SAFE — multiple parameters
async function searchUsers(name, minAge) {
const result = await pool.query(
'SELECT * FROM users WHERE name ILIKE $1 AND age >= $2',
[`%${name}%`, minAge]
);
return result.rows;
}
// The database driver handles escaping — you never build SQL strings manually
// This applies to any SQL database: PostgreSQL, MySQL, SQLiteSystem Design
System design questions in Node.js interviews test whether you can architect real applications. These are common for candidates with 2+ years of experience, especially at product companies and startups.
Q1: How would you design a real-time chat application using Node.js?
Why they ask: Real-time applications are where Node.js shines. This tests whether you understand WebSockets, pub/sub patterns, and how to scale beyond a single server.
// Architecture:
// Client ↔ Socket.io ↔ Node.js Server ↔ Redis (pub/sub) ↔ MongoDB (persistence)
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const Redis = require('ioredis');
const app = express();
const server = http.createServer(app);
const io = new Server(server, { cors: { origin: '*' } });
// Redis for pub/sub (enables multiple server instances)
const pub = new Redis();
const sub = new Redis();
sub.subscribe('chat');
sub.on('message', (channel, message) => {
io.emit('newMessage', JSON.parse(message));
});
io.on('connection', (socket) => {
console.log('User connected:', socket.id);
socket.on('joinRoom', (room) => {
socket.join(room);
});
socket.on('sendMessage', async (data) => {
// Persist to MongoDB
await Message.create(data);
// Publish to Redis (all server instances receive this)
pub.publish('chat', JSON.stringify(data));
});
socket.on('disconnect', () => {
console.log('User disconnected:', socket.id);
});
});
// Scaling: Redis pub/sub ensures messages reach users
// connected to ANY server instance, not just the one that
// received the message. This is how you scale WebSockets.Key architecture points: Socket.io for WebSocket connections, Redis pub/sub for multi-server communication, MongoDB for message persistence, and rooms for group chat isolation. Mention that without Redis, messages only reach users connected to the same server instance.
Q2: How do you handle file uploads in Node.js?
Why they ask: File uploads test your understanding of streams, memory management, and cloud storage integration. The key concern: large files should not be buffered entirely in memory.
const multer = require('multer');
const AWS = require('aws-sdk');
// Option 1: Store on disk
const diskStorage = multer.diskStorage({
destination: (req, file, cb) => cb(null, 'uploads/'),
filename: (req, file, cb) => {
const uniqueName = Date.now() + '-' + file.originalname;
cb(null, uniqueName);
}
});
// Option 2: Store in memory (for small files only)
const memoryStorage = multer({ storage: multer.memoryStorage() });
// Option 3: Stream directly to S3 (best for large files)
const s3 = new AWS.S3();
const upload = multer({ storage: diskStorage, limits: { fileSize: 5 * 1024 * 1024 } }); // 5MB limit
app.post('/api/upload', upload.single('file'), async (req, res) => {
// req.file contains the uploaded file info
const params = {
Bucket: process.env.S3_BUCKET,
Key: req.file.filename,
Body: require('fs').createReadStream(req.file.path) // Stream, not buffer
};
const result = await s3.upload(params).promise();
res.json({ url: result.Location });
});
// Memory considerations:
// - multer.memoryStorage() loads entire file into RAM — dangerous for large files
// - Disk storage + streaming to S3 is the production pattern
// - Always set file size limits to prevent memory exhaustionHow to Prepare — By Company Type
The depth of Node.js knowledge tested varies by company type. Here is what each expects and how long to prepare:
Startups (Razorpay, CRED, Swiggy, early-stage companies)
Preparation time: 2-3 weeks. Focus on event loop, Express.js, and MongoDB. Build a complete REST API from scratch — routing, middleware, authentication, database CRUD. Know the event loop phases, async/await error handling, and Mongoose. Startups want developers who can ship features fast, so practical API building skills matter most.
Product Companies (Flipkart, Myntra, PhonePe, Atlassian India)
Preparation time: 3-4 weeks. Add system design and testing to your preparation. Beyond API building, they test real-time architecture (WebSockets), caching strategies (Redis), message queues, and how you would design systems that handle millions of requests. Know testing with Jest/Mocha and understand performance profiling.
Service Companies (TCS, Infosys, Wipro, Cognizant)
Preparation time: 1-2 weeks. Focus on basics and REST API building. Know what the event loop is (high level), build a CRUD API with Express, connect to MongoDB, and explain middleware. Service companies test breadth over depth — they want to confirm you can work on Node.js projects, not that you understand V8 internals.
Practice With Real Interview Simulations
Reading Node.js questions is not the same as answering them under pressure. Practice with timed mock interviews that test your ability to explain the event loop, build Express APIs from memory, and discuss system design decisions.
TRY INTERVIEW PRACTICE →If you cannot explain the event loop in 60 seconds, you are not ready for a Node.js interview. It is the single question that separates developers who use Node from developers who understand it.
Node.js is the backbone of Indian startup engineering. Startups test event loop understanding and practical API building, product companies add system design and scaling questions, and service companies test the basics. The good news: Node.js interviews have a clear pattern. Master the event loop, build Express APIs fluently, understand async patterns deeply, and you will handle any Node.js interview in India. Every API you build in practice is one less you will struggle with in the interview.
Prepare for Your Node.js Interview
Practice with AI-powered mock interviews, get your resume ATS-ready, and walk into your next Node.js interview with confidence.
Free · AI-powered · Instant feedback
Related Reading
Interview Prep
Interview Coding Questions Java
What companies actually ask in Java coding rounds
15 min read
Resume Guide
Full Stack Developer Resume — India
Build a resume that showcases your Node.js and frontend skills
13 min read
Resume Guide
Software Developer Resume — India
For freshers and developers entering the startup ecosystem
11 min read