Buffer.concat()

Joins a list of Buffers into a single new Buffer, optionally truncated or padded to a given length.

Signature
Buffer.concat(list[, totalLength])
  • Added in Node.js v0.7.11

Parameters

ParameterTypeDescription
$listBuffer[] | Uint8Array[]List of Buffer or Uint8Array instances to concatenate.
$totalLengthoptionalinteger= the sum of the lengths in listTotal length of the Buffer instances in list when concatenated.

Return value

BufferA new Buffer containing the copied contents of every item in list, of length totalLength when that argument is given.

Errors

Throws ERR_INVALID_ARG_TYPE if list is not an array or contains a non-Buffer, non-Uint8Array value, and ERR_OUT_OF_RANGE if totalLength is negative.

Changelog

  • PHP v8.0.0The elements of list can now be Uint8Arrays, not only Buffers.

Buffer.concat() allocates one new buffer and copies every item in the list into it. It is the standard way to turn the chunks a stream emitted into a single value you can decode or parse.

Pass totalLength when you know it

Without totalLength, Node walks the list once to add up the lengths and again to copy. If you already tracked the running total — and a stream handler almost always has — passing it skips the first pass. On a list of thousands of small chunks that is a measurable win.

totalLength is a promise, not a hint

The result is always exactly totalLength bytes. Too small and the tail is silently truncated; too large and the remainder is zero-filled. A stale counter therefore corrupts your data without raising an error, which is the single most common bug on this function.

Cost

Every call copies. Concatenating inside a data handler makes the work quadratic in the number of chunks — push into an array and concatenate once at the end instead. Buffer.concat([]) returns an empty buffer, so an empty stream needs no special case.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Collect the chunks of a request or file stream into one Buffer before parsing.
  • Prepend a binary header to a payload in a single allocation.
  • Merge decrypted or decompressed blocks back into one value.
  • Zero-pad a short buffer out to a fixed record size.

Frequently asked questions

How do I combine multiple Buffers in Node.js?

Pass them as an array to Buffer.concat, as in Buffer.concat([a, b, c]). Collect chunks in an array first and concatenate once, rather than concatenating on every chunk, because each call copies all the data again.

What does the totalLength argument to Buffer.concat do?

It sets the exact length of the result, letting Node skip the pass that sums the input lengths. If it is smaller than the real total the data is truncated, and if it is larger the remainder is zero-filled.

Why is my concatenated Buffer missing bytes at the end?

Almost certainly because the totalLength you passed is smaller than the sum of the chunk lengths, which truncates without an error. Drop the argument or recompute the sum from the chunks you actually collected.

More examples

Buffering a request body

Chunks accumulate in an array and are joined exactly once, so the copying is linear rather than quadratic. The running length doubles as the totalLength argument and as the limit check, which is what stops an unbounded upload from exhausting memory before concat is ever reached.

Example · javascript
const http = require('node:http');

http.createServer((req, res) => {
  const chunks = [];
  let length = 0;

  req.on('data', (chunk) => {
    chunks.push(chunk);
    length += chunk.length;
    if (length > 1_000_000) req.destroy();   // cap before you allocate
  });

  req.on('end', () => {
    const body = Buffer.concat(chunks, length);
    res.end(`received ${body.length} bytes\n`);
  });
}).listen(3000);

Prepending a header in one allocation

One concat call produces the whole wire frame, so the payload is copied once instead of being written into a pre-sized buffer field by field. The last line shows mixed input types, which matters when part of your data comes from a Web API that returns Uint8Array.

Example · javascript
function envelope(payload) {
  const header = Buffer.alloc(4);
  header.writeUInt32BE(payload.length, 0);
  return Buffer.concat([header, payload], 4 + payload.length);
}

const packet = envelope(Buffer.from('hello'));
console.log(packet.toString('hex'));      // 0000000568656c6c6f
console.log(packet.readUInt32BE(0));      // 5
console.log(packet.subarray(4).toString()); // hello

// Uint8Arrays are accepted alongside Buffers since v8.
console.log(Buffer.concat([Buffer.from([1]), new Uint8Array([2, 3])]).length); // 3

Padding to a fixed size

Passing a totalLength larger than the data zero-fills the tail, which is a compact way to reach a fixed block size. The final line is the same mechanism working against you, and is why the explicit length check belongs in the function.

Example · javascript
function padTo(buf, size) {
  if (buf.length > size) throw new RangeError(`${buf.length} > ${size}`);
  return Buffer.concat([buf], size);   // remainder is zero-filled
}

console.log(padTo(Buffer.from('key'), 8).toString('hex')); // 6b65790000000000
console.log(padTo(Buffer.from('key'), 8).length);          // 8

// Without the guard, a long input is silently cut short:
console.log(Buffer.concat([Buffer.from('too-long')], 3).toString()); // too

Discussion

  • Be the first to comment on this lesson.