Node.js Streams Practice Questions with Solutions

Introduction

Streams are an important Node.js concept for working with data piece by piece instead of loading everything into memory at once. They are useful for large files, HTTP requests, uploads, downloads, and real-time data. In this chapter, you will practice readable, writable, duplex, and transform streams, along with pipe(), stream events, file streams, and practical examples. The questions start from simple concepts and gradually move toward real-world Node.js usage. Node.js Streams practice questions with solutions help to understand the concepts.

Question 1: What is a Readable Stream in Node.js?

Problem

Create a simple readable stream and read data from it.

Solution

const { Readable } = require("stream");

const readableStream = Readable.from([
    "Hello ",
    "from ",
    "Node.js ",
    "Streams!"
]);

readableStream.on("data", (chunk) => {

    console.log("Received:", chunk.toString());

});

readableStream.on("end", () => {

    console.log("Reading completed.");

});

Output

Received: Hello 
Received: from 
Received: Node.js 
Received: Streams!
Reading completed.

Step-by-Step Explanation

A readable stream is used when Node.js needs to read data.

We import Readable:

const { Readable } = require("stream");

Then create a readable stream:

const readableStream = Readable.from([
    "Hello ",
    "from ",
    "Node.js ",
    "Streams!"
]);

The data event is triggered when data is available:

readableStream.on("data", (chunk) => {
    console.log(chunk.toString());
});

When there is no more data, the end event runs:

readableStream.on("end", () => {
    console.log("Reading completed.");
});

Question 2: How do you create a Writable Stream?

Problem

Create a writable stream that receives data and displays it on the console.

Solution

const { Writable } = require("stream");

const writableStream = new Writable({

    write(chunk, encoding, callback) {

        console.log(
            "Received:",
            chunk.toString()
        );

        callback();
    }

});

writableStream.write("Hello ");
writableStream.write("Node.js ");
writableStream.write("Streams!");

writableStream.end();

Output

Received: Hello 
Received: Node.js 
Received: Streams!

Step-by-Step Explanation

A writable stream is used when an application needs to write data.

We create a writable stream:

const writableStream = new Writable({
    write(chunk, encoding, callback) {
        // Handle data
    }
});

The write() method receives the data.

The data arrives as a Buffer by default, so we convert it to text:

chunk.toString()

After processing the chunk, we call:

callback();

This tells the stream that the current write operation has completed.


Question 3: How do you read a file using a Readable Stream?

Problem

Read a text file using fs.createReadStream() and display its contents chunk by chunk.

Solution

First create message.txt:

Welcome to Node.js.
Streams allow us to process data in chunks.

Create index.js:

const fs = require("fs");

const readStream = fs.createReadStream(
    "message.txt",
    "utf8"
);

readStream.on("data", (chunk) => {

    console.log("Received chunk:");

    console.log(chunk);

});

readStream.on("end", () => {

    console.log("File reading completed.");

});

readStream.on("error", (error) => {

    console.log("Error:", error.message);

});

Output

The output depends on the file size and stream buffering, but it will contain the file’s text followed by:

File reading completed.

Step-by-Step Explanation

fs.createReadStream() creates a readable stream for a file:

const readStream = fs.createReadStream(
    "message.txt",
    "utf8"
);

The data event receives chunks:

readStream.on("data", (chunk) => {
    console.log(chunk);
});

The end event runs after the entire file has been read:

readStream.on("end", () => {
    console.log("File reading completed.");
});

The error event handles problems such as a missing file.


Question 4: How do you write data using a Writable File Stream?

Problem

Create a file and write multiple pieces of data using fs.createWriteStream().

Solution

const fs = require("fs");

const writeStream = fs.createWriteStream(
    "output.txt"
);

writeStream.write("Hello Node.js!\n");

writeStream.write("This is a writable stream.\n");

writeStream.write("Data is being written in chunks.\n");

writeStream.end();

writeStream.on("finish", () => {

    console.log("File writing completed.");

});

writeStream.on("error", (error) => {

    console.log("Error:", error.message);

});

Output

Console:

File writing completed.

A new file named output.txt is created:

Hello Node.js!
This is a writable stream.
Data is being written in chunks.

Step-by-Step Explanation

We create a writable file stream:

const writeStream = fs.createWriteStream(
    "output.txt"
);

Then write data:

writeStream.write("Hello Node.js!\n");

We can call .write() multiple times.

When we are finished:

writeStream.end();

The finish event runs when all data has been flushed to the underlying system:

writeStream.on("finish", () => {
    console.log("File writing completed.");
});

Question 5: How do you copy a large file using pipe()?

Problem

Copy one file to another using streams and pipe().

Solution

Create source.txt:

This is the source file.
It will be copied using Node.js streams.

Create index.js:

const fs = require("fs");

const readStream = fs.createReadStream(
    "source.txt"
);

const writeStream = fs.createWriteStream(
    "copy.txt"
);

readStream.pipe(writeStream);

writeStream.on("finish", () => {

    console.log("File copied successfully.");

});

readStream.on("error", (error) => {

    console.log("Read error:", error.message);

});

writeStream.on("error", (error) => {

    console.log("Write error:", error.message);

});

Output

File copied successfully.

A new file named copy.txt will contain the same data as source.txt.

Step-by-Step Explanation

First, create a readable stream:

const readStream = fs.createReadStream(
    "source.txt"
);

Then create a writable stream:

const writeStream = fs.createWriteStream(
    "copy.txt"
);

Connect them:

readStream.pipe(writeStream);

The data automatically flows from:

source.txt
     ↓
Readable Stream
     ↓
pipe()
     ↓
Writable Stream
     ↓
copy.txt

Question 6: How do you create a Transform Stream?

Problem

Create a Transform stream that converts incoming text to uppercase.

Solution

const {
    Readable,
    Transform
} = require("stream");

const upperCaseTransform = new Transform({

    transform(chunk, encoding, callback) {

        const text = chunk
            .toString()
            .toUpperCase();

        callback(null, text);
    }

});

const readableStream = Readable.from([
    "hello ",
    "node.js ",
    "streams!"
]);

readableStream
    .pipe(upperCaseTransform)
    .on("data", (chunk) => {

        console.log(chunk.toString());

    });

Output

HELLO 
NODE.JS 
STREAMS!

Step-by-Step Explanation

A Transform stream is a special type of stream that can:

Read data
   ↓
Change/process data
   ↓
Output changed data

We create a Transform stream:

const upperCaseTransform = new Transform({

The transform() method receives each chunk:

transform(chunk, encoding, callback) {

We convert the chunk to uppercase:

const text = chunk
    .toString()
    .toUpperCase();

Then send the transformed data forward:

callback(null, text);

Question 7: What is a Duplex Stream?

Problem

Create a simple Duplex stream that can both receive and provide data.

Solution

const { Duplex } = require("stream");

const duplexStream = new Duplex({

    read(size) {

        this.push("Data from readable side.");

        this.push(null);

    },

    write(chunk, encoding, callback) {

        console.log(
            "Received:",
            chunk.toString()
        );

        callback();
    }

});

duplexStream.on("data", (chunk) => {

    console.log(
        "Read:",
        chunk.toString()
    );

});

duplexStream.write("Hello from writable side.");

duplexStream.end();

Output

Read: Data from readable side.
Received: Hello from writable side.

Step-by-Step Explanation

A Duplex stream has both:

Readable side
Writable side

The read() method controls data coming out of the stream:

read(size) {
    this.push("Data from readable side.");
    this.push(null);
}

The write() method handles data coming into the stream:

write(chunk, encoding, callback) {
    console.log(chunk.toString());
    callback();
}

Simple Example

Think of a telephone:

You speak     → writable side
You listen    ← readable side

A Duplex stream can receive data and provide data independently.


Question 8: How do you monitor stream events?

Problem

Create a readable stream and use important stream events such as data, end, and error.

Solution

const fs = require("fs");

const readStream = fs.createReadStream(
    "message.txt",
    "utf8"
);

readStream.on("data", (chunk) => {

    console.log("DATA EVENT");

    console.log(chunk);

});

readStream.on("end", () => {

    console.log("END EVENT");

});

readStream.on("error", (error) => {

    console.log("ERROR EVENT");

    console.log(error.message);

});

Output

For a valid file, you will see something similar to:

DATA EVENT
Welcome to Node.js...
END EVENT

Step-by-Step Explanation

data

The data event is emitted when a chunk of data is available:

readStream.on("data", (chunk) => {
    console.log(chunk);
});

end

The end event occurs when there is no more data:

readStream.on("end", () => {
    console.log("END EVENT");
});

error

The error event handles stream errors:

readStream.on("error", (error) => {
    console.log(error.message);
});

Question 9: How do you control the flow of a readable stream?

Problem

Read a file and temporarily pause the stream after receiving a chunk, then continue it after one second.

Solution

const fs = require("fs");

const readStream = fs.createReadStream(
    "message.txt",
    "utf8"
);

readStream.on("data", (chunk) => {

    console.log("Received data:");

    console.log(chunk);

    readStream.pause();

    console.log("Stream paused.");

    setTimeout(() => {

        console.log("Stream resumed.");

        readStream.resume();

    }, 1000);

});

readStream.on("end", () => {

    console.log("Reading completed.");

});

Output

The exact number of chunks depends on the file size and stream buffering. You will see output similar to:

Received data:
...
Stream paused.
Stream resumed.
...
Reading completed.

Step-by-Step Explanation

The stream is normally flowing when we listen for data.

To pause it:

readStream.pause();

After one second:

readStream.resume();

starts the flow again.


Question 10: How do you build a practical file-processing pipeline using Streams?

Problem

Create a Node.js program that:

  • Reads a text file.
  • Converts all text to uppercase.
  • Writes the transformed content to another file.
  • Uses streams.
  • Uses a Transform stream.
  • Uses pipe().

Solution

Create input.txt:

welcome to node.js
streams are useful for large data
learning streams is important

Create index.js:

const fs = require("fs");

const {
    Transform
} = require("stream");

const readStream = fs.createReadStream(
    "input.txt",
    "utf8"
);

const writeStream = fs.createWriteStream(
    "output.txt"
);

const upperCaseStream = new Transform({

    transform(chunk, encoding, callback) {

        const text = chunk
            .toString()
            .toUpperCase();

        callback(null, text);
    }

});

readStream
    .pipe(upperCaseStream)
    .pipe(writeStream);

writeStream.on("finish", () => {

    console.log(
        "File processed successfully."
    );

});

readStream.on("error", (error) => {

    console.log(
        "Read error:",
        error.message
    );

});

upperCaseStream.on("error", (error) => {

    console.log(
        "Transform error:",
        error.message
    );

});

writeStream.on("error", (error) => {

    console.log(
        "Write error:",
        error.message
    );

});

Output

Console:

File processed successfully.

The generated output.txt contains:

WELCOME TO NODE.JS
STREAMS ARE USEFUL FOR LARGE DATA
LEARNING STREAMS IS IMPORTANT

Step-by-Step Explanation

Step 1: Create the readable stream

const readStream = fs.createReadStream(
    "input.txt",
    "utf8"
);

This reads the input file in chunks.

Step 2: Create the writable stream

const writeStream = fs.createWriteStream(
    "output.txt"
);

This writes data to the output file.

Step 3: Create the Transform stream

const upperCaseStream = new Transform({

The Transform stream changes the incoming text.

Step 4: Convert the chunk

const text = chunk
    .toString()
    .toUpperCase();

Step 5: Pass the transformed data forward

callback(null, text);

Step 6: Connect the streams

readStream
    .pipe(upperCaseStream)
    .pipe(writeStream);

This creates a complete pipeline:

input.txt
   ↓
Readable Stream
   ↓
Transform Stream
   ↓
Uppercase
   ↓
Writable Stream
   ↓
output.txt

Step 7: Detect completion

writeStream.on("finish", () => {
    console.log("File processed successfully.");
});

Key Takeaways

  • Streams allow Node.js to process data progressively instead of treating an entire data source as one large value.
  • A readable stream is used to read or provide data.
  • A writable stream is used to receive or write data.
  • A Duplex stream supports both reading and writing.
  • A Transform stream can modify data as it passes through.
  • fs.createReadStream() creates a readable file stream.
  • fs.createWriteStream() creates a writable file stream.
  • pipe() connects a readable stream to a writable stream or another compatible stream.
  • The data event is used when chunks of readable data become available.
  • The end event indicates that a readable stream has no more data to provide.
  • The finish event indicates that a writable stream has finished processing all written data.
  • The error event can be used to handle stream errors.
  • pause() temporarily stops a readable stream from flowing.
  • resume() continues a paused readable stream.
  • Streams are particularly useful for large files and continuous data.
  • Transform streams are useful for processing data between a source and destination.
  • Stream pipelines can connect multiple processing stages together.
  • Streams help reduce the need to load an entire large data source into memory at once.
  • Streams are widely used with files, HTTP requests, responses, uploads, and downloads.
  • Understanding streams is important for becoming comfortable with advanced Node.js programming.

FAQs

1. What are Streams in Node.js?

Streams are objects that allow Node.js to process data progressively, often in smaller pieces called chunks.

Instead of waiting for all data to become available, an application can start processing data as it arrives.

Common stream types are:

Readable
Writable
Duplex
Transform

2. What is a Readable Stream?

A Readable stream provides data that an application can consume.

For example:

const fs = require("fs");

const stream = fs.createReadStream(
    "large-file.txt"
);

stream.on("data", (chunk) => {
    console.log(chunk);
});

A file readable stream provides the contents of the file in chunks.

3. What is a Writable Stream?

A Writable stream receives data that an application wants to write somewhere.

For example:

const fs = require("fs");

const stream = fs.createWriteStream(
    "output.txt"
);

stream.write("Hello Node.js!");

stream.end();

The data is written to output.txt.

4. What is pipe() in Node.js Streams?

pipe() connects a readable stream to a writable stream or another compatible destination.

Example:

const fs = require("fs");

const readStream = fs.createReadStream(
    "source.txt"
);

const writeStream = fs.createWriteStream(
    "copy.txt"
);

readStream.pipe(writeStream);

Data flows automatically from the readable stream to the writable stream.

5. What is a Transform Stream?

A Transform stream is a type of Duplex stream that can modify data as it passes through.

For example, it can:

  • Convert text to uppercase
  • Compress data
  • Encrypt data
  • Modify JSON
  • Filter information

Example:

const { Transform } = require("stream");

The incoming data can be processed before being sent to the next stream.

6. Why are Streams useful for large files?

Streams allow data to be processed progressively.

Without streams, an application might attempt to load a large file into memory before processing it.

With streams:

Read chunk
   ↓
Process chunk
   ↓
Write chunk
   ↓
Read next chunk

This approach can reduce memory requirements and is useful when working with large files.

7. What is the difference between Duplex and Transform Streams?

Both can be used for reading and writing.

A Duplex stream has independent readable and writable sides.

A Transform stream is designed to process incoming data and produce corresponding output.

A simple way to remember them is:

Duplex:
Input ↔ Output

Transform:
Input → Process → Output

Written by Shubhranshu Shekhar, who has trained 20000+ students in coding.

Scroll to Top