25 Node.js Core questions with answers for mern stack roles — 10 easy, 10 medium, 5 hard.
About Node.js Core
Node.js runs JavaScript on the server using the V8 engine and libuv. This topic explains the event loop phases, non-blocking I/O, streams, buffers, EventEmitter, modules, the thread pool, worker threads and clustering. You will also see how to handle errors, configuration and graceful shutdown, which are common interview themes for backend and full stack roles.
Sheet progressStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
✓ 3 questions free • Unlock the rest in seconds
01
What is Node.js and how does it handle many requests on a single thread?
Node.js is a JavaScript runtime built on the V8 engine and libuv. Your JavaScript runs on one main thread, but I/O such as network calls, file reads and database queries is non-blocking. Node hands that work to the operating system or the libuv thread pool and continues with other requests; callbacks run when results are ready. This makes it efficient for I/O-heavy APIs, but weak for CPU-heavy work.
02
What is the difference between CommonJS and ES modules in Node.js?
CommonJS uses require and module.exports, loads synchronously and is the historic Node default. ES modules use import and export, are statically analysable, support top-level await and enable tree shaking. Node treats .mjs files, or .js files when package.json has type: module, as ESM. Modern Node versions can even require synchronous ES modules, but mixing systems still needs care, so new MERN projects usually pick ESM throughout.
A Buffer is a fixed-size chunk of raw binary memory allocated outside the V8 heap, used for files, network packets, images and encodings. You create one with Buffer.from('hello') or Buffer.alloc(10) and convert with toString('utf8'), toString('base64') or toString('hex'). Streams deliver data as Buffers by default. Buffer.allocUnsafe is faster but may contain old memory, so it must be filled before use.
04
What is the EventEmitter class and where is it used?
EventEmitter from the node:events module implements the publish-subscribe pattern. You register listeners with on or once and trigger them with emit, passing data. Many core objects extend it, including HTTP servers, streams and process. Always handle the error event, because an emitted error with no listener crashes the process. Remove listeners you no longer need to avoid the MaxListenersExceededWarning and memory leaks.
05
What is the difference between package.json and package-lock.json, and what do ^ and ~ mean?
package.json lists dependencies with version ranges, scripts and metadata. package-lock.json records the exact installed versions of every package, including nested ones, so every machine and CI build gets the same tree. Commit both and use npm ci in pipelines. A caret like ^4.18.2 allows minor and patch updates below 5.0.0; a tilde like ~4.18.2 allows only patch updates below 4.19.0.
06
How do you manage configuration and secrets differently for development and production in Node.js?
Keep config in environment variables, following the twelve-factor approach. Locally, load a .env file with dotenv or Node's built-in --env-file flag, and add .env to .gitignore while committing a .env.example. In production, inject variables through the hosting platform or a secrets manager. Validate all required variables at startup with a schema library like zod so the app fails fast with a clear message.
07
Create a basic HTTP server in Node.js without Express that returns JSON.
The built-in node:http module is enough for simple servers.
import http from 'node:http';
const server = http.createServer((req, res) => {
if (req.method === 'GET' && req.url === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ status: 'ok' }));
}
res.writeHead(404);
res.end();
});
server.listen(3000, () => console.log('Listening on 3000'));
Express adds routing, middleware and body parsing on top of this.
08
Read a JSON file and write an updated version using fs/promises and async/await.
Define scripts in package.json and run them with npm run.
"scripts": {
"dev": "node --watch --env-file=.env src/server.js",
"start": "node src/server.js",
"lint": "eslint .",
"test": "vitest run"
}
Modern Node has a built-in --watch flag, so nodemon is optional. npm start and npm test work without the run keyword.
11
What is the difference between process.nextTick, setImmediate and setTimeout(fn, 0)?
process.nextTick queues a callback to run right after the current operation, before Promise microtasks and before the event loop continues. setImmediate runs in the check phase after poll. setTimeout with 0 runs in the timers phase once at least about 1 ms has passed. Overusing nextTick recursively can starve I/O, so setImmediate is the safer way to defer heavy work.
Streams process data in chunks instead of loading it all into memory. The four types are Readable (file read, HTTP request), Writable (file write, HTTP response), Duplex (TCP socket) and Transform (gzip, encryption), which modifies data as it passes. Streams handle backpressure, pausing the source when the destination is slow. pipeline() from node:stream/promises connects them safely and handles errors and cleanup.
13
Your API becomes unresponsive whenever a user uploads an image for resizing. What is happening and how do you fix it?
Resizing is CPU-bound work running on the main thread, so it blocks the event loop and every other request waits. Fix it by moving the work off the main thread: use a worker_threads pool, a native library like sharp that works in the libuv pool, or better, push a job to a queue such as BullMQ with Redis and process it in a separate worker service, then notify the client.
14
Your script crashes with out of memory while reading a 5 GB log file. How would you rewrite it?
fs.readFile loads the whole file into memory, which exceeds the heap. Use a stream instead: fs.createReadStream with readline lets you process one line at a time with for await, keeping memory flat. If you need to transform and write output, connect read, transform and write streams with pipeline so backpressure and errors are handled. Increasing --max-old-space-size only hides the real problem.
15
How do you implement graceful shutdown for a Node.js API during deployments?
Listen for SIGTERM and SIGINT. When received, stop accepting new connections with server.close(), let in-flight requests finish, close database connections with mongoose.connection.close() and other resources, then exit with code 0. Set a timeout, for example 10 seconds, after which you force exit. In Kubernetes or behind a load balancer, also fail the readiness check first so traffic stops reaching the instance.
16
Your Node.js API runs on an 8-core server but CPU monitoring shows only one core busy. What do you do?
A Node process runs JavaScript on a single thread, so one instance uses about one core. Run multiple instances: PM2 in cluster mode with -i max, the cluster module, or several containers behind a load balancer such as Nginx. Because instances do not share memory, move sessions, caches and rate-limit counters to Redis, and make sure the app is stateless so any instance can serve any request.
17
Compress a large file with gzip using streams in Node.js.
Node 18 and later ship a global fetch. Use AbortSignal.timeout to cancel slow requests.
async function getJSON(url) {
try {
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
if (err.name === 'TimeoutError') console.error('Request timed out');
throw err;
}
}
Without a timeout, a hung upstream can hold connections open indefinitely.
19
Describe a production issue you handled in a Node.js service.
Situation: our order API started timing out every evening at peak. Task: as the on-call developer I had to restore it. Action: I checked logs and metrics, found event-loop lag spikes, and traced them to a synchronous CSV export running inside a request. I moved the export to a background job and added event-loop lag alerts. Result: p95 latency dropped from 4 seconds to 300 ms and timeouts stopped.
20
How do you plan a Node.js major version upgrade for a project your team maintains?
Situation: our services were on a Node version nearing end of life. Task: I owned the upgrade to the current LTS. Action: I read the changelog for breaking changes, upgraded locally, ran the test suite, fixed deprecated APIs and incompatible native packages, and rolled out to staging, then one production service, watching metrics. Result: all services moved within two sprints with no incidents, and I added an LTS calendar to our planning.
Each loop iteration goes through phases: timers runs expired setTimeout and setInterval callbacks; pending callbacks runs some deferred system callbacks; poll waits for and runs I/O callbacks; check runs setImmediate callbacks; close callbacks handles events like socket close. Between every callback, Node drains process.nextTick callbacks and then Promise microtasks. Knowing this explains why setImmediate inside an I/O callback always runs before a setTimeout of 0.
22
What is libuv and which operations use the Node.js thread pool?
libuv is the C library that provides Node's event loop and asynchronous I/O. Network I/O uses non-blocking OS mechanisms like epoll or IOCP, so it does not use the pool. The thread pool, four threads by default, handles file system calls, DNS lookup, zlib compression and crypto functions like pbkdf2 and scrypt. Heavy bcrypt or crypto load can saturate it; UV_THREADPOOL_SIZE can increase it.
23
Compare worker_threads, cluster and child_process in Node.js.
cluster forks multiple Node processes that share one server port, used to use all CPU cores for an HTTP app, though PM2 or containers often do this instead. child_process runs a separate program or script, for example ffmpeg, communicating through stdio or IPC. worker_threads run JavaScript in parallel threads inside one process and can share memory through SharedArrayBuffer, best for CPU-heavy tasks like image processing or parsing.
24
How should errors be handled in a Node.js application?
Separate operational errors, like a failed DB connection or invalid input, which you handle and respond to, from programmer errors, like undefined property access, which signal bugs. Use try/catch with async/await, always handle Promise rejections and stream error events. Listen to uncaughtException and unhandledRejection only to log and exit cleanly, then let a process manager restart the app, because state may be corrupted.
25
Memory usage of a production Node.js service keeps rising until it restarts. How do you investigate?
First confirm the trend with monitoring of heapUsed and RSS. Reproduce in staging under load, then take heap snapshots with --inspect and Chrome DevTools, or use --heapsnapshot-signal, and compare snapshots to find growing object types and their retainers. Common causes are unbounded in-memory caches, listeners added per request, global arrays and unclosed timers. Fix the root cause, add a bounded LRU cache, and set alerts.
Frequently asked questions
How important is Node.js Core in MERN Stack interviews?
Node.js runs JavaScript on the server using the V8 engine and libuv. This topic explains the event loop phases, non-blocking I/O, streams, buffers, EventEmitter, modules, the thread pool, worker threads and clustering. You will also see how to handle errors, configuration and graceful shutdown, which are common interview themes for backend and full stack roles.
How should I use the Node.js Core sheet?
Go top to bottom — easy to hard. Answer before opening each answer, tick what you solved and save misses to Last-Minute Revision.