Buffer.concat()
Joins a list of Buffers into a single new Buffer, optionally truncated or padded to a given length.
Buffer.concat(list[, totalLength])- Added in Node.js v0.7.11
Parameters
| Parameter | Type | Description |
|---|---|---|
$list | Buffer[] | Uint8Array[] | List of Buffer or Uint8Array instances to concatenate. |
$totalLengthoptional | integer= the sum of the lengths in list | Total 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
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.
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.
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); // 3Padding 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.
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