Streams & Buffers

Streams let Node process data piece by piece as it arrives instead of loading it all into memory at once, and Buffers are the fixed-length chunks of raw binary data that streams move around.

Learn Streams & Buffers in our free Node.js course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.

Part of the free Node.js course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

By the end of this lesson you'll know what a Buffer holds and how to convert it to and from text, you'll read a large file chunk by chunk with a readable stream, and you'll copy a file efficiently by piping one stream into another — all while keeping memory use flat.

What You'll Learn in This Lesson

1️⃣ Buffers: Chunks of Raw Bytes

Before streams, you need to understand what they carry. A Buffer is Node's container for raw binary data — a fixed-length sequence of bytes. Text, images, network packets: at the lowest level they're all just bytes, and a Buffer is how Node holds them.

Read the worked example below — every line is explained — then run it. Notice that printing the Buffer directly shows its bytes as hexadecimal numbers.

2️⃣ Readable Streams: Data Piece by Piece

A readable stream hands you a file (or a network response, or any source) in small chunks instead of all at once. You open one with fs.createReadStream(path) and then listen for two key events:

Why bother? Because fs.readFile loads the entire file into memory before you see a single byte. A 2 GB log file would need 2 GB of RAM. A stream processes that same file in ~64 KB chunks, so memory use stays low and constant no matter how big the file gets.

Your turn. The program below works once you fill in the three blanks marked ___ . Follow the 👉 hints, then run it and compare with the expected output.

3️⃣ Writable Streams & .pipe()

A writable stream is the opposite of a readable one: it's a destination you push chunks into . You create one with fs.createWriteStream(path) . The magic happens when you connect a readable stream to a writable one with .pipe() :

This is how you copy a multi-gigabyte file using only a tiny, constant amount of memory — the whole copy is just source.pipe(dest) .

Mini-Challenge: Count a File's Bytes

No blanks this time — just a brief and an outline to keep you on track. Stream a file, total up the byte length of each chunk, and print the result. Write it yourself, run it, and check your output against the example in the comments.

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Streams & Buffers

Concept

Node.js Syntax

Make a buffer

Buffer.from(str)

Buffer to string

buf.toString()

Open a read stream

fs.createReadStream(p)

Open a write stream

fs.createWriteStream(p)

Pipe one into another

src.pipe(dest)

Handle each chunk

stream.on('data', fn)

Detect the end

stream.on('end', fn)

Frequently Asked Questions

🎉 Lesson Complete!

Practice quiz

What does Buffer.from('hello').length return?

  • The number 5 (bytes)
  • The string 'hello'
  • The number of words
  • A hex dump

Answer: The number 5 (bytes). length is the number of bytes; 'hello' is 5 ASCII bytes.

Which method decodes a Buffer's bytes back into readable text?

  • buf.decode()
  • buf.toString()
  • buf.read()
  • buf.parse()

Answer: buf.toString(). buf.toString() decodes bytes (UTF-8 by default) into a string.

Why prefer fs.createReadStream over fs.readFile for a huge file?

  • It is always faster on tiny files
  • It validates the file format
  • It keeps memory low by reading in chunks
  • It encrypts the data

Answer: It keeps memory low by reading in chunks. A stream processes ~64KB chunks, so memory stays flat instead of loading the whole file.

Which event fires once after the last chunk of a readable stream?

  • 'finish'
  • 'close'
  • 'data'
  • 'end'

Answer: 'end'. 'end' fires once on the readable side after the final chunk.

What does source.pipe(dest) automatically handle for you?

  • Backpressure
  • File encryption
  • JSON parsing
  • DNS lookups

Answer: Backpressure. pipe() pauses the source when the destination can't keep up — that's backpressure.

Why must you attach a 'error' listener to every stream?

  • To rename the file
  • Stream errors are emitted, and unhandled ones crash the process
  • To speed up reads
  • To enable utf8 mode

Answer: Stream errors are emitted, and unhandled ones crash the process. Stream errors aren't thrown; an unhandled 'error' event crashes Node.

How many bytes does Buffer.from('hello')[0] (the byte for 'h') equal?

  • 72
  • 5
  • 104
  • 0

Answer: 104. 'h' is 0x68, which is 104 in decimal.

What event fires on the WRITABLE side when all data is flushed?

  • 'end'
  • 'finish'
  • 'data'
  • 'drain'

Answer: 'finish'. The writable side emits 'finish' once every byte has been written.

Passing 'utf8' to createReadStream makes each chunk arrive as what?

  • A string
  • A raw Buffer
  • A number
  • An array

Answer: A string. With an encoding set, chunks are strings instead of Buffers.

Which is true about a Buffer's .length for the text '😀'?

  • It equals the character count
  • It is always 1
  • It counts bytes, so an emoji is more than 1
  • It throws an error

Answer: It counts bytes, so an emoji is more than 1. length counts bytes; a single emoji takes several UTF-8 bytes.

Continue this course