Practice Node.js
You’ve read the lessons — now prove it. Take the quiz for instant feedback, then work the coding exercises. Try each one yourself before you reveal the solution.
Quick quiz
17 questions- 1
Which function is used to load a module in CommonJS (CJS) Node.js?
- 2
What does
module.exportsrefer to in a CommonJS module? - 3
Which field in
package.jsondefines the entry point for a CommonJS package? - 4
What is the correct way to install a package as a dev dependency?
- 5
Which built-in Node.js module provides file-system operations such as reading and writing files?
- 6
Given the code below, what is printed first? ``
js console.log('A'); setTimeout(() => console.log('B'), 0); Promise.resolve().then(() => console.log('C')); console.log('D'); - 7
In the Node.js event loop, which queue is processed before
setTimeoutcallbacks? - 8
How do you emit a custom event called
'data'on an EventEmitter instanceemitter? - 9
Which of the following correctly reads the value of an environment variable called
PORT? - 10
What does
fs.createReadStream()return? - 11
Which
httpmodule method creates an HTTP server that listens for incoming requests? - 12
Which class must you extend to create a custom Transform stream in Node.js?
- 13
What module do you import to create a Worker thread in Node.js?
- 14
What does
process.nextTick(fn)guarantee about whenfnis called? - 15
Which
osmodule method returns the number of logical CPU cores available on the machine? - 16
In Node.js, when both
setTimeout(fn, 0)andsetImmediate(fn)are scheduled inside an I/O callback, which fires first? - 17
What is the correct method to safely create a new Buffer of
nzero-filled bytes in modern Node.js?
Coding exercises
8 tasksHello, Module!
EasyCreate a CommonJS module that exports a function greet(name) returning the string "Hello, <name>!".
Then, in the same file (after the export), call greet with your own name and log the result to the console.
// greet.js
function greet(name) {
// TODO: return the greeting string
}
module.exports = { greet };
// Call greet here and log the result
💡 Show hint
Use a template literal or string concatenation to build the greeting, then use console.log() to print it.
✅ Show solution
// greet.js
function greet(name) {
return `Hello, ${name}!`;
}
module.exports = { greet };
// Quick smoke-test
const { greet: g } = module.exports;
console.log(g("Alice")); // Hello, Alice!
Read a File with `fs`
EasyWrite a Node.js script that:
1. Uses fs.readFile to asynchronously read a file called notes.txt in the same directory.
2. If an error occurs (e.g. the file does not exist), logs "Error: " + err.message.
3. On success, logs the file contents as a UTF-8 string.
const fs = require("fs");
const path = require("path");
const filePath = path.join(__dirname, "notes.txt");
// TODO: read the file asynchronously
💡 Show hint
Pass "utf8" as the second argument to fs.readFile so the callback receives a string instead of a Buffer.
✅ Show solution
const fs = require("fs");
const path = require("path");
const filePath = path.join(__dirname, "notes.txt");
fs.readFile(filePath, "utf8", (err, data) => {
if (err) {
console.log("Error: " + err.message);
return;
}
console.log(data);
});
Simple HTTP Server
MediumBuild an HTTP server using Node.js's built-in http module that:
- Listens on port 3000.
- Responds to any request with status 200, Content-Type: text/plain, and the body "Hello from Node.js!".
- Logs "Server running on http://localhost:3000" once it starts.
Do not use Express or any third-party package.
const http = require("http");
const PORT = 3000;
const server = http.createServer((req, res) => {
// TODO: send the response
});
server.listen(PORT, () => {
// TODO: log the startup message
});
💡 Show hint
Use res.writeHead(200, { "Content-Type": "text/plain" }) then res.end("Hello from Node.js!") inside the request callback.
✅ Show solution
const http = require("http");
const PORT = 3000;
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello from Node.js!");
});
server.listen(PORT, () => {
console.log("Server running on http://localhost:3000");
});
Custom EventEmitter — Task Queue
MediumUsing Node.js's built-in events module:
1. Create a class TaskQueue that extends EventEmitter.
2. Add a method addTask(name) that emits a "task:added" event with the task name.
3. Add a method completeTask(name) that emits a "task:done" event with the task name.
4. Instantiate TaskQueue, register listeners that log "Added: <name>" and "Done: <name>", then add and complete two tasks.
const { EventEmitter } = require("events");
class TaskQueue extends EventEmitter {
addTask(name) {
// TODO: emit "task:added"
}
completeTask(name) {
// TODO: emit "task:done"
}
}
const queue = new TaskQueue();
// TODO: register listeners and call addTask / completeTask
💡 Show hint
Inside addTask, call this.emit("task:added", name). Register listeners with queue.on("task:added", (name) => { ... }).
✅ Show solution
const { EventEmitter } = require("events");
class TaskQueue extends EventEmitter {
addTask(name) {
this.emit("task:added", name);
}
completeTask(name) {
this.emit("task:done", name);
}
}
const queue = new TaskQueue();
queue.on("task:added", (name) => {
console.log("Added: " + name);
});
queue.on("task:done", (name) => {
console.log("Done: " + name);
});
queue.addTask("Write tests");
queue.addTask("Deploy app");
queue.completeTask("Write tests");
queue.completeTask("Deploy app");
Async File Stats with `async`/`await`
HardWrite an async Node.js CLI tool that:
1. Reads a directory path from process.argv[2] (default to "." if not provided).
2. Uses fs.promises.readdir to list all entries in the directory.
3. For each entry, uses fs.promises.stat to determine whether it is a file or directory.
4. Prints a summary table to the console in this format (one entry per line):
[FILE] notes.txt or [DIR] src
5. At the end, prints "Total: X file(s), Y dir(s)".
6. Wraps everything in a try/catch and logs a user-friendly error message if anything fails.
const fs = require("fs").promises;
const path = require("path");
async function summarise(dirPath) {
// TODO: list entries, stat each one, print summary
}
const target = process.argv[2] || ".";
summarise(target);
💡 Show hint
Use Promise.all(entries.map(...)) so all stat calls run concurrently instead of one-by-one. Check stat.isDirectory() to decide the label.
✅ Show solution
const fs = require("fs").promises;
const path = require("path");
async function summarise(dirPath) {
try {
const entries = await fs.readdir(dirPath);
const stats = await Promise.all(
entries.map(async (entry) => {
const fullPath = path.join(dirPath, entry);
const stat = await fs.stat(fullPath);
return { name: entry, isDir: stat.isDirectory() };
})
);
let files = 0;
let dirs = 0;
for (const { name, isDir } of stats) {
if (isDir) {
console.log("[DIR] " + name);
dirs++;
} else {
console.log("[FILE] " + name);
files++;
}
}
console.log(`\nTotal: ${files} file(s), ${dirs} dir(s)`);
} catch (err) {
console.error("Could not read directory: " + err.message);
}
}
const target = process.argv[2] || ".";
summarise(target);
Custom Transform Stream — Line Counter
MediumUsing Node.js's built-in stream module:
1. Create a Transform stream called LineCounter that counts newline characters as data flows through it.
2. The stream should pass the original data downstream unchanged.
3. After the stream finishes, log "Total lines: <n>" using the finish event.
4. Pipe process.stdin through your LineCounter to a writable process.stdout.
Run it with: node solution.js < somefile.txt
const { Transform } = require("stream");
class LineCounter extends Transform {
constructor() {
super();
this.count = 0;
}
_transform(chunk, encoding, callback) {
// TODO: count newlines and pass chunk through
}
_flush(callback) {
// TODO: log the total line count
callback();
}
}
const counter = new LineCounter();
process.stdin.pipe(counter).pipe(process.stdout);
💡 Show hint
Inside _transform, use chunk.toString().split("\n").length - 1 to count newlines in the chunk, then call callback(null, chunk) to pass data through. Use _flush to log the final count.
✅ Show solution
const { Transform } = require("stream");
class LineCounter extends Transform {
constructor() {
super();
this.count = 0;
}
_transform(chunk, encoding, callback) {
const text = chunk.toString();
const newlines = (text.match(/\n/g) || []).length;
this.count += newlines;
callback(null, chunk);
}
_flush(callback) {
process.stderr.write("Total lines: " + this.count + "\n");
callback();
}
}
const counter = new LineCounter();
process.stdin.pipe(counter).pipe(process.stdout);
Worker Thread — Parallel Fibonacci
HardUsing the worker_threads module, compute Fibonacci numbers in parallel:
1. In the main thread, create an array of inputs [35, 36, 37, 38, 39].
2. Spawn one Worker per input, each computing the naive recursive Fibonacci for its value.
3. Collect all results with Promise.all and log them in input order (not completion order).
4. Log "All done in <ms>ms" when finished.
Put the worker logic in the same file using the isMainThread flag.
const { Worker, isMainThread, workerData, parentPort } = require("worker_threads");
if (isMainThread) {
const inputs = [35, 36, 37, 38, 39];
function runWorker(n) {
// TODO: return a Promise that resolves with the fibonacci result
}
async function main() {
// TODO: run all workers in parallel and log results
}
main();
} else {
// TODO: compute fibonacci for workerData.n and post the result
}
💡 Show hint
Use new Worker(__filename, { workerData: { n } }) to spawn each worker pointing at the same file. In the worker branch, implement a simple recursive fib(n) function and call parentPort.postMessage(fib(workerData.n)).
✅ Show solution
const { Worker, isMainThread, workerData, parentPort } = require("worker_threads");
function fib(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
if (isMainThread) {
const inputs = [35, 36, 37, 38, 39];
function runWorker(n) {
return new Promise((resolve, reject) => {
const w = new Worker(__filename, { workerData: { n } });
w.on("message", resolve);
w.on("error", reject);
w.on("exit", (code) => {
if (code !== 0) reject(new Error("Worker exited with code " + code));
});
});
}
async function main() {
const start = Date.now();
const results = await Promise.all(inputs.map(runWorker));
inputs.forEach((n, i) => console.log("fib(" + n + ") = " + results[i]));
console.log("All done in " + (Date.now() - start) + "ms");
}
main();
} else {
parentPort.postMessage(fib(workerData.n));
}
Graceful Shutdown with SIGTERM
HardBuild a Node.js HTTP server that shuts down gracefully:
1. Create an http.Server listening on port 4000.
2. On every request, respond with 200 OK and the body "Hello!".
3. Listen for SIGTERM — when received, stop accepting new connections with server.close(), then log "Server closed" and call process.exit(0) inside the close callback.
4. Also listen for unhandledRejection — log "Unhandled rejection:" + the reason, then call process.exit(1).
Test graceful shutdown with: kill -SIGTERM <pid>
const http = require("http");
const server = http.createServer((req, res) => {
// TODO: respond with 200 and body "Hello!"
});
server.listen(4000, () => {
console.log("Listening on http://localhost:4000");
});
// TODO: handle SIGTERM
// TODO: handle unhandledRejection
💡 Show hint
Use process.on("SIGTERM", () => { server.close(() => { ... }); }). Inside the server.close callback is the earliest moment you know all in-flight requests have finished.
✅ Show solution
const http = require("http");
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello!");
});
server.listen(4000, () => {
console.log("Listening on http://localhost:4000");
});
process.on("SIGTERM", () => {
console.log("SIGTERM received — closing server");
server.close(() => {
console.log("Server closed");
process.exit(0);
});
});
process.on("unhandledRejection", (reason) => {
console.error("Unhandled rejection:", reason);
process.exit(1);
});