Node.js Buffer

The Buffer class gives Node.js a way to work directly with raw binary data outside the V8 heap, which is essential for handling files, network protocols, and streams.

What Is a Buffer?

A Buffer is a fixed-length chunk of memory allocated outside the JavaScript engine's regular memory heap. Unlike a JavaScript string, which is always encoded as UTF-16 internally, a Buffer stores raw bytes with no assumption about encoding. This makes Buffers the natural data type for anything that isn't plain text: image data read from disk, TCP packets arriving over a socket, or the contents of a ZIP file.

Creating Buffers

Node.js exposes several static methods for constructing a Buffer, and choosing the right one matters for both correctness and performance.

  • Buffer.from(source) - builds a buffer from an existing string, array, or another buffer.
  • Buffer.alloc(size) - allocates a buffer of the given size and fills it with zeros, so it is always safe to read from immediately.
  • Buffer.allocUnsafe(size) - allocates a buffer faster than alloc(), but the memory may contain old, leftover data until you write to it.

Example 1: Creating buffers three different ways

// Creating buffers three different ways
const fromString = Buffer.from('Hello Node.js', 'utf-8');
const fromArray = Buffer.from([72, 101, 121]); // byte values for "Hey"
const zeroed = Buffer.alloc(8); // 8 bytes, all zeroed out

console.log(fromString); // <Buffer 48 65 6c 6c 6f 20 4e 6f 64 65 2e 6a 73>
console.log(fromArray.toString()); // "Hey"
console.log(zeroed); // <Buffer 00 00 00 00 00 00 00 00>
console.log(zeroed.length); // 8

Converting Buffers to Strings

Because a Buffer only holds bytes, you must tell it how to interpret those bytes when converting back to text. The toString() method accepts an encoding argument, and optionally a start and end byte offset so you can decode just a slice of the buffer.

Example 2: Decoding a buffer with different encodings

const buf = Buffer.from('Node.js Buffers', 'utf-8');

console.log(buf.toString('utf-8'));  // "Node.js Buffers"
console.log(buf.toString('hex'));    // "4e6f64652e6a73204275666665727"
console.log(buf.toString('base64')); // "Tm9kZS5qcyBCdWZmZXJz"

// Decoding only the first 4 bytes
console.log(buf.toString('utf-8', 0, 4)); // "Node"
EncodingDescription
utf-8Default text encoding; variable-width, safe for most human-readable text
hexEach byte becomes two hex characters; common for hashes and tokens
base64Compact ASCII-safe representation; common for embedding binary in text formats
ascii7-bit encoding; strips the high bit, only safe for plain ASCII text
latin11 byte per character (0-255); useful for legacy binary-as-string data
Note: Prefer Buffer.alloc() over Buffer.allocUnsafe() unless you are certain you will immediately overwrite every byte. The performance gain from allocUnsafe() rarely matters outside hot loops, and the safety of a zero-filled buffer prevents leaking old memory contents.

Combining, Slicing, and Comparing Buffers

Buffers support array-like operations for joining multiple chunks together, extracting a portion without copying the underlying memory, and comparing two buffers byte by byte.

Example 3: Concatenating, slicing, and comparing buffers

const a = Buffer.from('foo ');
const b = Buffer.from('bar');
const combined = Buffer.concat([a, b]);

console.log(combined.toString()); // "foo bar"

const slice = combined.subarray(0, 3);
console.log(slice.toString()); // "foo"

console.log(Buffer.compare(a, b)); // 1 (a sorts after b)
console.log(a.equals(Buffer.from('foo '))); // true
Note: Slicing a buffer by byte index is not the same as slicing a string by character index. Multi-byte UTF-8 characters (like emoji or accented letters) can span 2-4 bytes, so cutting a buffer in the wrong place can split a character and produce garbled or invalid text when decoded.

Exercise: Node.js Buffer

What is the primary purpose of the Buffer class in Node.js?