Perfect 👍 let’s make this a Node.js Interview Q&A Cheatsheet with brief but solid answers you can quickly review.
1. What is Node.js?
- Node.js is a runtime environment built on V8 JavaScript engine that allows running JS outside the browser.
- It uses an event-driven, non-blocking I/O model → great for scalable apps.
2. Difference between Node.js and JavaScript in browser?
- Browser JS → manipulates DOM, no access to filesystem.
- Node.js → backend tasks (files, DB, network). No DOM.
3. What is npm?
- Node Package Manager, comes with Node.js.
dependencies→ required in production.devDependencies→ needed only for development (tests, build tools).
4. CommonJS vs ES Modules?
- CommonJS →
require(), synchronous. - ES Modules →
import/export, asynchronous, modern standard.
5. What are callbacks?
- Functions passed as arguments, executed later (async).
- Can lead to callback hell → solved using Promises/async-await.
6. What is the Event Loop?
- Mechanism that allows Node.js to handle non-blocking I/O in a single thread.
- Phases: timers → I/O callbacks → idle → poll → check → close.
Great question 🚀. The Event Loop is one of the most important concepts in Node.js, and understanding it will make a lot of things about async behavior (like setTimeout, promises, file I/O, etc.) crystal clear.
- Node.js runs in a single thread (unlike Java or C# web servers that spawn many threads).
- To handle thousands of concurrent requests, it uses the event loop.
- The event loop continuously checks: “Do I have something to execute? Are there callbacks waiting? Do I have timers expired?”
That’s how Node.js achieves non-blocking I/O.
The loop executes in this order:
- Timers → executes
setTimeout,setIntervalcallbacks. - I/O callbacks → handles I/O finished tasks (e.g., network, filesystem).
- Idle, prepare → internal stuff.
- Poll → waits for new I/O events (like incoming requests).
- Check → executes setImmediate callbacks.
- Close callbacks → executes
socket.on('close', ...)etc.
Let’s see code to watch it in action:
const fs = require("fs");
const net = require("net");
console.log("1. Start");
// ---------------- PHASE 1: TIMERS ----------------
setTimeout(() => {
console.log("4. setTimeout callback (Timers phase)");
}, 0);
// ---------------- PHASE 5: CHECK ----------------
setImmediate(() => {
console.log("6. setImmediate callback (Check phase)");
});
// ---------------- PHASE 2: I/O CALLBACKS ----------------
// Async file read will trigger callback in I/O phase
fs.readFile(__filename, () => {
console.log("5. fs.readFile callback (I/O callbacks phase)");
});
// ---------------- MICROTASK QUEUE ----------------
// Runs before event loop continues
Promise.resolve().then(() => {
console.log("3. Promise.then callback (Microtask queue)");
});
process.nextTick(() => {
console.log("2. process.nextTick callback (Microtask - highest priority)");
});
// ---------------- TIMERS (INTERVAL) ----------------
let count = 0;
let timer = setInterval(() => {
count++;
console.log("Interval run:", count);
if (count === 3) {
clearInterval(timer); // stop after 3 times
}
}, 0); // runs in Timers phase repeatedly
// ---------------- PHASE 4: POLL ----------------
// Poll is usually where I/O waits; we simulate it
setTimeout(() => {
console.log("Extra Poll simulation (via timeout 10ms)");
}, 10);
// ---------------- PHASE 6: CLOSE CALLBACKS ----------------
const server = net.createServer().listen(0);
server.on("connection", (socket) => socket.end());
server.close(() => {
console.log("7. server close callback (Close callbacks phase)");
});
console.log("8. End");The output will look like this (order may vary slightly depending on system, but promises always run before timers/immediate):
1. Start
8. End
2. process.nextTick callback (Microtask - highest priority)
3. Promise.then callback (Microtask queue)
4. setTimeout callback (Timers phase)
Interval run: 1
5. fs.readFile callback (I/O callbacks phase)
6. setImmediate callback (Check phase)
Extra Poll simulation (via timeout 10ms)
7. server close callback (Close callbacks phase)
Interval run: 2
Interval run: 3
1. Start(synchronous code)2. process.nextTick callback (Microtask - highest priority)3. Promise.then callback (Microtask queue)4. End(sync finish)
Event loop phases start:
Interval run: 1(Timers phase, since interval=0)6. setTimeout callback (Timers phase)(0ms timer)7. setImmediate callback (Check phase)8. File read completed (Poll -> I/O callbacks phase)Interval run: 2(next Timers phase cycle)9. Poll phase (simulated by timeout inside poll)Interval run: 3(Timers phase again)10. server close callback (Close callbacks phase)
- setInterval(..., 0) still waits until the next timers phase (it won’t block).
- Execution order of setTimeout(…, 0) and setInterval(…, 0) can sometimes flip because they both sit in the same timers queue.
- In practice, you’ll almost always see setTimeout first, then setInterval.
✅ Key Takeaways:
process.nextTickalways wins inside the current tick.Promise.thenruns afternextTick, before timers.setInterval(..., 0)will run every timers phase (not continuously).setTimeout(..., 0)fires once in the first timers phase.setImmediateexecutes in check phase, which usually comes after timers if no blocking I/O.fs.readFilecallback executes in the I/O callbacks phase (poll).server.closeexecutes in the close callbacks phase, always last.
7. What are Streams?
- Handle data piece by piece instead of loading all at once.
- Types: Readable, Writable, Duplex, Transform.
8. What are Buffers?
- Temporary storage for binary data.
- Useful for files, network packets, etc.
9. How does Node.js handle concurrency if single-threaded?
- Uses event loop + libuv thread pool for async I/O.
- Heavy CPU tasks → offloaded to worker threads.
10. process.nextTick() vs setImmediate()?
process.nextTick()→ runs before next event loop iteration.setImmediate()→ runs after current event loop completes.
11. What is middleware in Express?
- Functions that run between request and response.
- Example: logging, authentication, error handling.
12. Common built-in modules?
fs,path,http,os,events,crypto,util.
13. Sync vs Async functions?
- Sync → blocks execution until finished.
- Async → non-blocking, executed later via callbacks/promises.
14. How does error handling work?
try/catchfor sync & async/await..catch()for Promises.- Event emitter’s
errorevent for streams.
15. What is clustering in Node.js?
- Running multiple Node.js processes to use multi-core CPUs.
- Each process has its own event loop.
16. Difference from multi-threaded servers (Java/.NET)?
- Node.js uses single-thread + async I/O.
- Java/.NET use multiple threads for concurrency.
17. fork() vs spawn() vs exec()?
spawn→ runs command, streams output.exec→ runs command, buffers output in memory.fork→ creates a new Node.js process for IPC.
18. JWT authentication?
- JSON Web Tokens → signed tokens used for stateless authentication.
- Stored in headers (Authorization: Bearer).
19. What is CORS?
- Restricts cross-origin requests.
- In Node.js, handle via
corsmiddleware.
20. REST vs GraphQL?
- REST → fixed endpoints, multiple round trips.
- GraphQL → single endpoint, query exact data.
21. How does V8 work?
- Compiles JS → machine code (JIT).
- Handles memory allocation + garbage collection.
22. Node.js memory management?
- Managed by V8: heap + stack.
- GC removes unused objects.
libuv is a small C library that Node.js uses to provide:
-
A cross-platform event loop (works the same on Linux/macOS/Windows).
-
A unified abstraction over OS I/O mechanisms
- Linux:
epoll, BSD/macOS:kqueue, Windows:IOCP, etc.
- Linux:
-
A work queue + thread pool for operations that can’t be made non-blocking at the OS level (e.g., many filesystem calls,
getaddrinfo()DNS lookups, compression, crypto).
Think of libuv as Node’s “engine room”: it waits for events (sockets, timers), dispatches callbacks, and offloads blocking work to a small pool of worker threads so the single JavaScript thread stays responsive.
-
async/awaitis JavaScript syntax over Promises. It doesn’t create threads. -
When you
awaitsomething that ultimately touches the OS (e.g.,fs.promises.readFile), Node’s C++ bindings call into libuv:- The JS call returns a Promise immediately.
- libuv submits a request (often to its thread pool).
- The event loop keeps running.
- When the work finishes, libuv posts a completion back to the main loop.
- Node settles the Promise (resolve/reject) → V8 schedules a microtask.
- Your
asyncfunction resumes afterawaiton the same JS thread.
So: libuv handles the I/O and threading, V8 handles the Promise/microtask resumption, and async/await is just the nice syntax you write.
Some operations cannot be watched with non-blocking OS events; they would block the loop if run directly. libuv offloads these to its worker pool (default size 4, configurable with UV_THREADPOOL_SIZE before Node starts):
- Most filesystem ops:
readFile,writeFile,stat,readdir, etc. (POSIX FS calls are blocking; offloaded to keep the loop free.) - DNS
dns.lookup()(uses the system resolver viagetaddrinfo()→ blocking → thread pool). (Contrast:dns.resolve()uses c-ares and non-blocking sockets, not the pool.) - Crypto heavy hitters:
crypto.pbkdf2,scrypt,bcrypt(via native addons), somezlibcompression. - Misc CPU-ish native work scheduled via
uv_queue_work().
Networking (TCP/UDP/HTTP) usually doesn’t use the thread pool: sockets are non-blocking and are watched by the event loop (epoll/kqueue/IOCP).
Note: On Windows, libuv uses IOCP even for many filesystem operations; abstraction still ensures the JS thread doesn’t block.
async function load() {
const text = await fs.promises.readFile("data.txt", "utf8");
console.log(text);
}
load();- JS enters
load(), creates a Promise. - Node’s FS binding calls
uv_fs_read(libuv). - libuv queues the read onto the thread pool.
- Event loop keeps spinning; your JS thread is free.
- Worker thread does the blocking
read()syscall. - When done, the worker posts a completion to the loop.
- Node resolves the Promise → microtask runs →
awaitresumes →console.log(text).
Default pool size is 4. If you run several CPU-heavy pool jobs, you can starve other pool users (like FS). Classic demo:
const fs = require('fs/promises');
const crypto = require('crypto');
for (let i = 0; i < 4; i++) {
crypto.pbkdf2('p', 's', 1e6, 64, 'sha512', () => {
console.log('pbkdf2 done', i);
});
}
// This FS read may be delayed until a worker is free:
fs.readFile('big.txt').then(() => console.log('file read done'));Because there are 4 pbkdf2 jobs and the pool size is 4, the file read can wait. Fixes/options:
- Increase pool:
UV_THREADPOOL_SIZE=8 node app.js(max 128 in modern Node; older docs mention up to 1024 for libuv but Node clamps lower—keep it reasonable). - Avoid heavy CPU work in the pool; move CPU-bound work to Worker Threads (true parallel JS).
- Pipeline / rate-limit your heavy tasks.
- After each libuv phase (timers, I/O, check, etc.), Node/V8 drain microtasks (Promises) and
process.nextTick. process.nextTickruns before Promise microtasks.- Pool completions land in the I/O callbacks phase, which then resolve Promises → your
awaitresumes as a microtask.
Priority (within a tick) roughly:
- Current callback’s sync code
process.nextTickqueue- Promise microtasks
- Move to next libuv phase (timers → I/O → check → close…)
- Event loop: watches sockets/timers; dispatches callbacks.
- Thread pool: does blocking FS, DNS, crypto/zlib, custom native work.
- async/await: Promise syntax; resumes when libuv signals completion → Promise settled → microtask → continue.
- Don’t assume
async/await= multithreading. JS still runs on one thread (unless you use Worker Threads). - For I/O-bound work:
async/await+ libuv is perfect. - For CPU-bound work: prefer Worker Threads or processes; don’t flood the libuv pool.
- If you mix heavy crypto/compression with lots of FS: tune
UV_THREADPOOL_SIZEand/or isolate heavy work.
// Runs 10 reads concurrently; each read uses a pool worker,
// but they complete as workers free up.
await Promise.all(
Array.from({ length: 10 }, (_, i) =>
fs.promises.readFile(`files/${i}.txt`, 'utf8')
)
);This doesn’t create 10 JS threads; it schedules 10 libuv requests, and the pool + OS handle them without blocking the JS thread.
Node.js is best for:
-
I/O-bound tasks (network, database, filesystem)
- Handling thousands of simultaneous connections with minimal threads.
- Examples: HTTP APIs, WebSockets, streaming, chat servers.
-
Real-time applications
- Chat apps, live dashboards, collaborative tools, notifications.
-
Single-page application backends
- APIs serving JSON, microservices.
The key: Node.js shines when you have many concurrent operations but most of the time they are waiting on I/O.
- Node.js runs JavaScript on a single thread (main thread).
- Even though it has a libuv thread pool, it’s limited (default 4 threads).
- Heavy computation can block the event loop, making all requests slow.
Examples:
- Image/video processing
- Complex math / machine learning tasks
- Large encryption / decryption tasks
- Any operation that takes seconds on the main thread
Problem: All other requests (HTTP, timers) will stall until computation is done.
Solution: Offload heavy CPU work to:
- Worker Threads (
worker_threadsmodule) - External services / microservices in another language (e.g., Python, C++)
// BAD: blocks the event loop
const data = fs.readFileSync("bigfile.txt"); // blocks for seconds- Any blocking synchronous API (e.g.,
readFileSync,crypto.pbkdf2Sync) will freeze Node for all clients. - Async alternatives exist (
fs.promises.readFile,crypto.pbkdf2) → Node remains responsive.
- Node.js is single-threaded by default.
- If you need multiple cores to compute in parallel without splitting tasks manually, Node.js is cumbersome.
Alternatives:
- Java, Go, Rust, C++ (true multithreaded concurrency built-in)
- Node.js keeps all objects in V8 heap, which is limited (~1.5GB for 64-bit default).
- Very large in-memory datasets can crash the process.
Alternatives: Languages/runtime with better memory control (Java, .NET, Rust).
| Use case | Node.js suitability |
|---|---|
| High concurrency, I/O-bound | ✅ Ideal |
| CPU-bound / heavy computation | ❌ Not ideal |
| Long blocking sync operations | ❌ Not ideal |
| Large in-memory datasets | ⚠ Limited |
| Real-time apps / chat / websockets | ✅ Ideal |
If your workload mostly waits on I/O, Node.js is excellent. If your workload mostly computes, Node.js may block the event loop, so consider Worker Threads or another language/runtime.
24. Load balancing in Node.js?
- Use clustering, PM2, or Nginx to distribute requests.
25. Worker threads?
- Run CPU-heavy tasks in parallel threads.
- Useful for image processing, encryption, ML.
26. How to scale Node.js apps?
- Vertical scaling → more CPU/memory.
- Horizontal scaling → clusters, containers, load balancers.
27. How to secure Node.js apps?
- Use Helmet, sanitize inputs, HTTPS, JWT expiry, rate limiting.
28. Performance optimization?
- Caching (Redis), clustering, async I/O, streams, avoid sync code.
29. What is async_hooks?
- API to track async resources like Promises, timers.
30. Debugging Node.js apps?
- Use
console.log,node --inspect, Chrome DevTools, or Winston/PM2 logs.
Q: Write a basic HTTP server (no Express).
const http = require('http');
http.createServer((req, res) => {
res.end('Hello Node.js');
}).listen(3000);Q: Read/write file in Node.js.
const fs = require('fs');
fs.readFile('input.txt', 'utf8', (err, data) => console.log(data));
fs.writeFile('output.txt', 'Hello World', () => console.log('Done'));Q: Convert callback → async/await.
const fs = require('fs/promises');
async function readFile() {
const data = await fs.readFile('test.txt', 'utf8');
console.log(data);
}Q: Stream large file.
const fs = require('fs');
fs.createReadStream('bigfile.txt').pipe(fs.createWriteStream('copy.txt'));