Skip to main content

Buffer Basics: Working with Binary Data in Node.js

When you work with files, network streams, or any raw data in Node.js, you're dealing with binary data—sequences of bytes that represent everything from text files to images to video streams. Node.js provides the Buffer class specifically designed to handle this binary data efficiently and safely.

Think of a Buffer like a container for raw data—similar to how an array holds numbers or strings, but Buffers hold raw bytes. Unlike JavaScript strings that are optimized for text, Buffers give you direct access to the binary representation of data, which is essential when working with protocols, file systems, or anything that isn't pure text.

What You Need to Know First

This is a foundational article—you don't need any prior knowledge of binary data or Buffers to understand it.

However, you should be comfortable with:

  • JavaScript/Node.js basics: Variables, functions, arrays, and basic syntax
  • Node.js environment: How to run Node.js scripts and see console output
  • Command line basics: How to execute Node.js commands

If you're completely new to Node.js, we recommend familiarizing yourself with basic JavaScript first.

What We'll Cover in This Article

By the end of this guide, you'll understand:

  • What Buffers are and why Node.js needs them
  • The relationship between Buffer, Uint8Array, and ArrayBuffer
  • How to create Buffers using different methods
  • The difference between safe and unsafe Buffer creation
  • When to use Buffers vs regular JavaScript strings or arrays

What We'll Explain Along the Way

We'll introduce these concepts with full explanations:

  • Binary data and bytes (with visual examples)
  • The difference between text and binary data
  • Memory allocation in Node.js
  • Why JavaScript alone isn't enough for binary data
  • The global Buffer object and how to import it

Why Node.js Needs Buffers

The Text vs Binary Problem

JavaScript was originally designed for web browsers where most data is text—HTML, CSS, JavaScript code, JSON. JavaScript strings handle text beautifully, but they're not designed for raw binary data.

What is Binary Data?

Binary data is information stored as a sequence of bytes (8-bit values from 0 to 255). Everything on a computer is ultimately binary data:

  • Text files: Each character is one or more bytes
  • Images: Pixel data, headers, metadata—all bytes
  • Videos: Frame data, audio tracks, compression info
  • Network packets: Raw data transmitted between computers
  • File streams: Reading or writing files byte-by-byte

Why Can't We Just Use Strings?

// ❌ Strings are problematic for binary data

// Problem 1: Strings assume UTF-16 encoding (2 bytes per character)
const binaryData = "\x00\x01\x02\x03"; // Looks like 4 bytes
console.log(binaryData.length); // 4 characters, but how many bytes?

// Problem 2: Some byte values aren't valid characters
const invalidString = "\xFF\xFE\xFD"; // These might get corrupted

// Problem 3: Strings are immutable and inefficient for byte manipulation
let data = "Hello";
data[0] = "J"; // This doesn't work!
console.log(data); // Still "Hello"

// Problem 4: Strings can't represent all byte values
const nullByte = "\x00"; // Null byte might terminate strings in some contexts

How Buffers Solve This:

// ✅ Buffers handle binary data correctly

// Solution 1: Direct byte access and representation
const buffer = Buffer.from([0, 1, 2, 3]); // 4 bytes exactly
console.log(buffer.length); // 4 bytes (clear and predictable)

// Solution 2: All byte values (0-255) are valid
const allBytes = Buffer.from([255, 254, 253]); // No corruption
console.log(allBytes); // <Buffer ff fe fd>

// Solution 3: Buffers are mutable—you can change bytes
const mutableBuffer = Buffer.from("Hello");
mutableBuffer[0] = 0x4a; // Change 'H' (0x48) to 'J' (0x4A)
console.log(mutableBuffer.toString()); // "Jello"

// Solution 4: Null bytes and control characters are just data
const withNull = Buffer.from([72, 101, 0, 108, 111]); // "He\0lo"
console.log(withNull.length); // 5 bytes (null byte is preserved)

Real-World Scenarios Where Buffers Are Essential

1. Reading Files

import { readFile } from "node:fs/promises";

// Reading an image file
const imageData = await readFile("photo.jpg");
// imageData is a Buffer containing the raw JPEG bytes
console.log(imageData.length); // File size in bytes
console.log(imageData.slice(0, 3)); // <Buffer ff d8 ff> (JPEG header)

2. Network Communication

import { createServer } from "node:http";

// HTTP server receiving POST data
createServer((req, res) => {
const chunks: Buffer[] = [];

// Data arrives as Buffer chunks
req.on("data", (chunk: Buffer) => {
chunks.push(chunk);
});

req.on("end", () => {
// Combine all Buffer chunks
const fullData = Buffer.concat(chunks);
console.log(`Received ${fullData.length} bytes`);
});
}).listen(3000);

3. Stream Processing

import { createReadStream, createWriteStream } from "node:fs";

// Reading a large video file in chunks
const readStream = createReadStream("video.mp4", {
highWaterMark: 64 * 1024, // 64 KB chunks
});

readStream.on("data", (chunk: Buffer) => {
// Process each chunk as it arrives
console.log(`Processing ${chunk.length} bytes`);
// Each chunk is a Buffer, not a string
});

Understanding Buffer Architecture

Before we create Buffers, let's understand what they are under the hood.

The Three-Layer Structure

Node.js Buffers are built on three interconnected concepts:

┌─────────────────────────────────────┐
│ Buffer (Node.js) │ ← High-level API with convenience methods
│ - toString(), slice(), write() │
│ - readInt16(), writeUInt32() │
└─────────────────────────────────────┘
↓ extends
┌─────────────────────────────────────┐
│ Uint8Array (JavaScript) │ ← Typed array for byte access
│ - array[0], array[1], array[2] │
│ - forEach(), map(), filter() │
└─────────────────────────────────────┘
↓ views
┌─────────────────────────────────────┐
│ ArrayBuffer (Raw Memory) │ ← Actual memory storage
│ - Fixed-size block of bytes │
│ - Cannot be accessed directly │
└─────────────────────────────────────┘

1. ArrayBuffer: The Foundation

ArrayBuffer is the actual block of memory where bytes are stored. You cannot read or write to it directly—it's just storage.

// Create raw memory: 8 bytes, all set to 0
const arrayBuffer = new ArrayBuffer(8);

// You cannot access bytes directly
console.log(arrayBuffer[0]); // undefined (no direct access)
console.log(arrayBuffer.byteLength); // 8 (only property available)

2. Uint8Array: The View

Uint8Array is a "view" that lets you read and write individual bytes in the ArrayBuffer.

// Create a view over the ArrayBuffer
const uint8 = new Uint8Array(arrayBuffer);

// Now you can access individual bytes
uint8[0] = 255;
uint8[1] = 128;
uint8[2] = 64;

console.log(uint8[0]); // 255
console.log(uint8.length); // 8

3. Buffer: The Node.js Enhancement

Buffer extends Uint8Array with additional methods specifically useful for Node.js operations.

// Create a Buffer (automatically creates ArrayBuffer and Uint8Array internally)
const buffer = Buffer.from([255, 128, 64]);

// All Uint8Array features work
console.log(buffer[0]); // 255
console.log(buffer.length); // 3

// Plus Buffer-specific methods
console.log(buffer.toString("hex")); // "ff8040"
buffer.writeInt16LE(1000, 0); // Write 16-bit integer

Visual Representation:

Memory (ArrayBuffer):
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ Raw bytes (8 bytes)
└───┴───┴───┴───┴───┴───┴───┴───┘

Uint8Array View:
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ Index positions
└───┴───┴───┴───┴───┴───┴───┴───┘
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
Read/Write individual bytes

Buffer Layer:
- toString() → Convert to text
- slice() → Create views
- write() → Insert data
- readInt16LE() → Read as 16-bit integer

Accessing the Underlying ArrayBuffer

Every Buffer has an underlying ArrayBuffer that you can access:

const buffer = Buffer.from([1, 2, 3, 4, 5]);

// Access the underlying ArrayBuffer
console.log(buffer.buffer); // ArrayBuffer { ... }
console.log(buffer.buffer.byteLength); // Might be larger than buffer.length!

// Why might it be larger?
console.log(buffer.length); // 5 (Buffer's used bytes)
console.log(buffer.buffer.byteLength); // 8192 (Allocated memory from pool)

// The Buffer uses only part of the ArrayBuffer

Important Concept: Buffer vs buffer.buffer

const buf = Buffer.alloc(10);

// buf.length → How many bytes the Buffer uses
console.log(buf.length); // 10

// buf.buffer.byteLength → Total allocated memory
console.log(buf.buffer.byteLength); // Might be 8192 (from pool)

// Why the difference?
// Node.js allocates memory from a pool for efficiency
// Your Buffer uses 10 bytes, but sits in a larger memory block

Creating Buffers: Four Methods

Node.js provides four primary ways to create Buffers, each with specific use cases.

Method 1: Buffer.from() - Create from Existing Data

Purpose: Convert existing data (arrays, strings, other buffers) into a Buffer.

From an Array of Bytes:

// Create a Buffer from an array of byte values (0-255)
const buffer1 = Buffer.from([72, 101, 108, 108, 111]); // "Hello"

console.log(buffer1); // <Buffer 48 65 6c 6c 6f>
console.log(buffer1.toString()); // "Hello"
console.log(buffer1.length); // 5 bytes

// What happens internally:
// 1. Node.js allocates memory for 5 bytes
// 2. Each array value is written as one byte
// 3. Values are automatically truncated to 0-255 range

Handling Values Outside 0-255 Range:

// Values are automatically truncated using (value & 255)
const buffer2 = Buffer.from([257, 257.5, -255, "1"]);

// 257 → 257 & 255 = 1 (only keep last 8 bits)
// 257.5 → Floor to 257 → 257 & 255 = 1
// -255 → -255 & 255 = 1
// "1" → Converted to number 1 → 1

console.log(buffer2); // <Buffer 01 01 01 01>
console.log(buffer2[0]); // 1
console.log(buffer2[1]); // 1

// Visual explanation of truncation:
// 257 in binary: 1 0000 0001 (9 bits)
// & 255 (mask): 0 1111 1111
// Result: 0 0000 0001 = 1

From a String:

// Create a Buffer from a UTF-8 string (default encoding)
const buffer3 = Buffer.from("Hello");

console.log(buffer3); // <Buffer 48 65 6c 6c 6f>
console.log(buffer3.length); // 5 bytes (1 byte per ASCII character)

// UTF-8 multi-byte characters
const buffer4 = Buffer.from("tést"); // 'é' is 2 bytes in UTF-8

console.log(buffer4); // <Buffer 74 c3 a9 73 74>
console.log(buffer4.length); // 5 bytes (not 4!)
console.log(buffer4.toString()); // "tést"

// Breakdown:
// 't' → 0x74 (1 byte)
// 'é' → 0xc3 0xa9 (2 bytes in UTF-8)
// 's' → 0x73 (1 byte)
// 't' → 0x74 (1 byte)

Different Encodings:

// UTF-8 encoding (default - supports all Unicode)
const utf8Buffer = Buffer.from("tést", "utf8");
console.log(utf8Buffer); // <Buffer 74 c3 a9 73 74> (5 bytes)

// Latin-1 encoding (1 byte per character, limited character set)
const latin1Buffer = Buffer.from("tést", "latin1");
console.log(latin1Buffer); // <Buffer 74 e9 73 74> (4 bytes)

// Why different?
// UTF-8: 'é' = 0xC3 0xA9 (2 bytes)
// Latin-1: 'é' = 0xE9 (1 byte)

// Hex encoding (2 characters = 1 byte)
const hexBuffer = Buffer.from("48656c6c6f", "hex");
console.log(hexBuffer.toString()); // "Hello"

// Base64 encoding
const base64Buffer = Buffer.from("SGVsbG8=", "base64");
console.log(base64Buffer.toString()); // "Hello"

From Another Buffer (Copy):

// Create a copy of an existing Buffer
const original = Buffer.from("Hello");
const copy = Buffer.from(original);

// Modify the copy
copy[0] = 0x4a; // Change 'H' to 'J'

console.log(original.toString()); // "Hello" (unchanged)
console.log(copy.toString()); // "Jello" (modified)

// These are separate buffers in memory
console.log(original === copy); // false

Method 2: Buffer.alloc() - Create Initialized Buffer

Purpose: Allocate a new Buffer of specified size with all bytes initialized to zero (or a specified value). This is the safe method.

Basic Usage:

// Create a 10-byte Buffer, all bytes set to 0
const buffer = Buffer.alloc(10);

console.log(buffer); // <Buffer 00 00 00 00 00 00 00 00 00 00>
console.log(buffer.length); // 10
console.log(buffer[0]); // 0
console.log(buffer[9]); // 0

// What happens internally:
// 1. Allocate 10 bytes of memory
// 2. Set ALL bytes to 0
// 3. Return the initialized Buffer
// This is SAFE - no old data can leak

Initialize with a Specific Value:

// Create a 10-byte Buffer, all bytes set to 1
const buffer1 = Buffer.alloc(10, 1);

console.log(buffer1); // <Buffer 01 01 01 01 01 01 01 01 01 01>

// Create a 5-byte Buffer, all bytes set to 0xFF (255)
const buffer2 = Buffer.alloc(5, 0xff);

console.log(buffer2); // <Buffer ff ff ff ff ff>
console.log(buffer2[0]); // 255

// Fill with a string pattern
const buffer3 = Buffer.alloc(10, "abc");

console.log(buffer3); // <Buffer 61 62 63 61 62 63 61 62 63 61>
console.log(buffer3.toString()); // "abcabcabca" (pattern repeated)

Why Use Buffer.alloc()?

// ✅ SAFE: Buffer.alloc() prevents data leaks
const safeBuffer = Buffer.alloc(10);
console.log(safeBuffer); // <Buffer 00 00 00 00 00 00 00 00 00 00>
// Guaranteed to be all zeros - no old password data, no sensitive info

// Use alloc() when:
// - Creating buffers for user input
// - Handling sensitive data
// - Security is more important than performance
// - You want predictable, clean memory

Performance Characteristics:

// Buffer.alloc() is slower because it initializes memory
console.time("Buffer.alloc");
for (let i = 0; i < 1000000; i++) {
const buf = Buffer.alloc(1024);
}
console.timeEnd("Buffer.alloc");
// Output: ~50-100ms (varies by system)

// Why slower?
// 1. Allocate memory
// 2. Zero out ALL 1024 bytes
// 3. Return buffer
// The zeroing step takes time but ensures safety

Method 3: Buffer.allocUnsafe() - Create Uninitialized Buffer

Purpose: Allocate a new Buffer of specified size WITHOUT initializing the bytes. This is faster but potentially unsafe because it may contain old data.

Basic Usage:

// Create a 10-byte Buffer WITHOUT zeroing memory
const buffer = Buffer.allocUnsafe(10);

console.log(buffer);
// <Buffer 6a f8 3c 01 00 00 00 00 c8 42>
// Random old data! Could be anything!

// What happens internally:
// 1. Allocate 10 bytes of memory
// 2. Return immediately WITHOUT initialization
// 3. Memory contains whatever was there before

Why Is It Unsafe?

// Demonstration of data leakage risk
function processPassword(password: string) {
// Store password in a buffer temporarily
const passwordBuffer = Buffer.from(password);
// ... do something with it ...
// Buffer goes out of scope but memory remains
}

processPassword("SuperSecret123!");

// Later, someone allocates memory unsafely
const unsafeBuffer = Buffer.allocUnsafe(20);
console.log(unsafeBuffer.toString());
// Might contain: "SuperSecret123!xxxxx" - leaked password!

// This is why it's called "unsafe"

When to Use allocUnsafe():

// ✅ SAFE use of allocUnsafe(): Immediately overwrite all bytes
const buffer = Buffer.allocUnsafe(10);
buffer.fill(0); // Immediately zero out
console.log(buffer); // <Buffer 00 00 00 00 00 00 00 00 00 00>

// ✅ SAFE: You're going to write data to every byte anyway
const buffer2 = Buffer.allocUnsafe(4);
buffer2.writeInt32LE(1234567, 0); // Overwrite all 4 bytes
console.log(buffer2); // <Buffer 87 d6 12 00>

// ✅ SAFE: Reading data from file (overwrites entire buffer)
import { readSync, openSync } from "node:fs";
const fd = openSync("data.bin", "r");
const buffer3 = Buffer.allocUnsafe(1024);
readSync(fd, buffer3, 0, 1024, 0); // File data overwrites buffer

// ❌ UNSAFE: Not overwriting all bytes
const unsafeBuffer = Buffer.allocUnsafe(10);
unsafeBuffer[0] = 65; // Only set one byte
console.log(unsafeBuffer.toString()); // "A" + random garbage

Performance Comparison:

// Compare Buffer.alloc() vs Buffer.allocUnsafe()
console.time("Buffer.alloc");
for (let i = 0; i < 1000000; i++) {
const buf = Buffer.alloc(1024);
}
console.timeEnd("Buffer.alloc");
// Output: ~50-100ms

console.time("Buffer.allocUnsafe");
for (let i = 0; i < 1000000; i++) {
const buf = Buffer.allocUnsafe(1024);
}
console.timeEnd("Buffer.allocUnsafe");
// Output: ~10-20ms (significantly faster!)

// Why faster?
// allocUnsafe() skips the memory initialization step
// Can be 2-5x faster for large buffers

Making allocUnsafe() Safe:

// Pattern: Allocate + Fill = Safe and fast
const buffer = Buffer.allocUnsafe(1000);
buffer.fill(0); // Manually zero out

// This is still faster than Buffer.alloc() for large sizes
// But safer than using uninitialized memory

Method 4: Buffer.allocUnsafeSlow() - Bypass Memory Pool

Purpose: Allocate memory directly from the system heap, bypassing Node.js's internal memory pool. Used for very specific performance scenarios.

Basic Usage:

// Allocate buffer outside the memory pool
const buffer = Buffer.allocUnsafeSlow(10);

console.log(buffer); // <Buffer ... random data ...>
console.log(buffer.length); // 10

// Difference from allocUnsafe():
// allocUnsafe() → Uses buffer pool (for buffers < 4096 bytes)
// allocUnsafeSlow() → Always allocates from system heap

When to Use allocUnsafeSlow():

// ✅ Use case: Large buffers that shouldn't tie up the pool
const largeBuffer = Buffer.allocUnsafeSlow(10 * 1024 * 1024); // 10 MB
// Allocated from heap, not pool
// Pool remains available for smaller allocations

// ✅ Use case: Buffer with long lifetime
const persistentBuffer = Buffer.allocUnsafeSlow(1024);
// Won't block pool space for other allocations

// ❌ Usually DON'T use for small, short-lived buffers
const smallBuffer = Buffer.allocUnsafeSlow(64); // Inefficient!
// Better to use allocUnsafe() which uses the pool

Comparison:

// Benchmark: Small buffer allocation
console.time("allocUnsafe (pooled)");
for (let i = 0; i < 1000000; i++) {
const buf = Buffer.allocUnsafe(1024); // Uses pool
}
console.timeEnd("allocUnsafe (pooled)");
// Faster for small buffers

console.time("allocUnsafeSlow (heap)");
for (let i = 0; i < 1000000; i++) {
const buf = Buffer.allocUnsafeSlow(1024); // Direct heap allocation
}
console.timeEnd("allocUnsafeSlow (heap)");
// Slower due to system allocation overhead

Comparing Buffer Creation Methods

Let's see all four methods side-by-side with their characteristics:

// Method 1: Buffer.from() - Convert existing data
const fromArray = Buffer.from([72, 101, 108, 108, 111]);
console.log(fromArray.toString()); // "Hello"
// Use when: You have data to convert (array, string, buffer)

// Method 2: Buffer.alloc() - Safe, initialized
const allocated = Buffer.alloc(5);
console.log(allocated); // <Buffer 00 00 00 00 00>
// Use when: Security matters, predictable memory needed

// Method 3: Buffer.allocUnsafe() - Fast, uninitialized
const unsafe = Buffer.allocUnsafe(5);
console.log(unsafe); // <Buffer ?? ?? ?? ?? ??> (random)
// Use when: Performance critical, will overwrite immediately

// Method 4: Buffer.allocUnsafeSlow() - Bypass pool
const slow = Buffer.allocUnsafeSlow(5);
console.log(slow); // <Buffer ?? ?? ?? ?? ??> (random)
// Use when: Large or long-lived buffer, don't want to use pool

Decision Tree:

Do you have existing data to convert?
├─ YES → Use Buffer.from(data)
└─ NO → Need to allocate empty buffer
├─ Security important? → Use Buffer.alloc(size)
└─ Performance critical?
├─ Small buffer (<4KB) → Use Buffer.allocUnsafe(size) + fill()
└─ Large buffer → Use Buffer.allocUnsafeSlow(size) + fill()

Common Pitfalls and Solutions

Pitfall 1: Confusing Buffer.length with String Length

// Problem: Character count ≠ byte count
const text = "café"; // 4 characters
const buffer = Buffer.from(text, "utf8");

console.log(text.length); // 4 characters
console.log(buffer.length); // 5 bytes (é is 2 bytes in UTF-8)

// Solution: Always check buffer.length for byte count
console.log(`"${text}" is ${text.length} characters`);
console.log(`"${text}" is ${buffer.length} bytes in UTF-8`);

// More examples:
const emoji = "😀"; // 1 character (emoji)
const emojiBuffer = Buffer.from(emoji);
console.log(emoji.length); // 2 (JavaScript counts as UTF-16 code units)
console.log(emojiBuffer.length); // 4 (UTF-8 bytes: 0xF0 0x9F 0x98 0x80)

Pitfall 2: Modifying Buffers from slice()

// Problem: slice() creates a VIEW, not a copy
const original = Buffer.from("Hello");
const sliced = original.slice(0, 3);

// Modifying the slice modifies the original!
sliced[0] = 0x4a; // Change 'H' to 'J'

console.log(original.toString()); // "Jello" (original changed!)
console.log(sliced.toString()); // "Jel"

// Solution: Use Buffer.from() to create a true copy
const original2 = Buffer.from("Hello");
const copied = Buffer.from(original2.slice(0, 3));

copied[0] = 0x4a;
console.log(original2.toString()); // "Hello" (unchanged)
console.log(copied.toString()); // "Jel"

Pitfall 3: Using allocUnsafe() Without Initialization

// ❌ DANGER: Uninitialized buffer may leak sensitive data
function createBuffer() {
const buf = Buffer.allocUnsafe(10);
// BUG: Not initializing all bytes!
buf[0] = 65; // Only setting first byte
return buf;
}

const result = createBuffer();
console.log(result.toString()); // "A" + random garbage (possibly sensitive data)

// ✅ SOLUTION: Always initialize ALL bytes
function createBufferSafe() {
const buf = Buffer.allocUnsafe(10);
buf.fill(0); // Zero out everything
buf[0] = 65; // Now safe to set specific bytes
return buf;
}

const result2 = createBufferSafe();
console.log(result2.toString()); // "A" + zeros (safe)

Pitfall 4: Assuming String Encoding

// Problem: Default encoding is UTF-8, but data might be different
const latin1Data = Buffer.from([0x74, 0xe9, 0x73, 0x74]); // "tést" in Latin-1

console.log(latin1Data.toString()); // "tést" (wrong! decoded as UTF-8)
console.log(latin1Data.toString("latin1")); // "tést" (correct!)

// Solution: Always specify encoding when you know the source
const utf8String = latin1Data.toString("utf8"); // If you expect UTF-8
const latin1String = latin1Data.toString("latin1"); // If you expect Latin-1
const hexString = latin1Data.toString("hex"); // If you want hex representation

Pitfall 5: Buffer Overflow When Writing

// Problem: Writing beyond buffer boundaries
const buffer = Buffer.alloc(5);

try {
// This will NOT throw an error, but will be truncated
buffer.write("Hello World"); // Only "Hello" fits
console.log(buffer.toString()); // "Hello" (silently truncated)
} catch (error) {
// This won't catch anything!
}

// Solution: Check return value and buffer size
const buffer2 = Buffer.alloc(5);
const bytesWritten = buffer2.write("Hello World");

console.log(`Wrote ${bytesWritten} bytes`); // 5
console.log(`Buffer size: ${buffer2.length} bytes`); // 5
console.log(`Data: ${buffer2.toString()}`); // "Hello"

if (bytesWritten < "Hello World".length) {
console.log("⚠️ Warning: Not all data was written!");
}

Summary: Key Takeaways

Understanding Buffers:

  • Buffers are containers for binary data - raw bytes that represent any kind of information
  • JavaScript strings aren't enough - they're designed for text, not binary data
  • Buffers extend Uint8Array - they have all array-like features plus Node.js-specific methods
  • Every Buffer has an underlying ArrayBuffer - the actual memory storage

Creating Buffers:

  • Buffer.from() - Convert existing data (arrays, strings, buffers) into a Buffer
  • Buffer.alloc() - Create a safe, zero-initialized buffer (slower but secure)
  • Buffer.allocUnsafe() - Create fast buffer without initialization (must overwrite all bytes)
  • Buffer.allocUnsafeSlow() - Allocate from heap, bypassing the buffer pool (for large/long-lived buffers)

When to Use Each Method:

  • Use Buffer.from() when you have data to convert
  • Use Buffer.alloc() when security and predictability matter
  • Use Buffer.allocUnsafe() when performance is critical AND you'll overwrite all bytes
  • Use Buffer.allocUnsafeSlow() for large or long-lived buffers

Common Mistakes to Avoid:

  • Don't confuse character count with byte count (UTF-8 characters can be multiple bytes)
  • Remember that slice() creates views, not copies - modifications affect the original
  • Always initialize allocUnsafe() buffers completely before use
  • Specify encoding explicitly when converting to strings
  • Check buffer size and return values when writing data

What's Next?

Now that you understand Buffer basics, you're ready to dive deeper:

  • Buffer Memory Management - Learn about buffer pools, memory allocation strategies, and performance optimization
  • Buffer Operations - Master reading, writing, copying, and manipulating binary data
  • Buffer Encoding and Conversion - Understand character encodings, base64, hex, and data transformations