Buffer.compare()
Compares two Buffers byte by byte and returns -1, 0 or 1 for use in sorting.
Buffer.compare(buf1, buf2)- Added in Node.js v0.11.13
Parameters
| Parameter | Type | Description |
|---|---|---|
$buf1 | Buffer | Uint8Array | The first value to compare. |
$buf2 | Buffer | Uint8Array | The second value to compare. |
Return value
integerEither -1, 0, or 1, depending on the result of the comparison: -1 when buf1 sorts before buf2, 0 when they are identical, 1 when buf1 sorts after buf2.
Errors
Throws ERR_INVALID_ARG_TYPE if either argument is not a Buffer or Uint8Array.
Changelog
- PHP v8.0.0The arguments can now be Uint8Arrays, not only Buffers.
Buffer.compare() orders two buffers by their bytes, comparing position by position until they differ. Because it returns exactly -1, 0 or 1, it can be handed straight to Array.prototype.sort() with no wrapper.
How the ordering works
Bytes are compared as unsigned values, 0 to 255. If one buffer is a prefix of the other, the shorter one sorts first: Buffer.from('ab') comes before Buffer.from('abc'). For ASCII text this matches a plain string sort, but for anything else it does not — byte order is not locale-aware, and 'Z' (0x5A) sorts before 'a' (0x61).
Static versus instance
buf1.compare(buf2) does the same thing, and the instance form additionally accepts targetStart, targetEnd, sourceStart and sourceEnd so you can compare regions without slicing. For a plain equality test, buf1.equals(buf2) is clearer than checking for 0.
Not for secrets
Comparison stops at the first differing byte, so its running time leaks where two values diverge. When comparing tokens, signatures or MACs, use crypto.timingSafeEqual() instead.
Example
When to use it
- Sort an array of binary keys or hashes into a deterministic order.
- Order records by a binary sort key read from a database or index.
- Deduplicate a list of buffers after sorting them.
- Assert two buffers are identical in a test.
Frequently asked questions
How do I check if two Buffers are equal in Node.js?
Use buf1.equals(buf2), which returns a boolean, or check Buffer.compare(buf1, buf2) === 0. For secrets such as tokens or HMACs use crypto.timingSafeEqual instead, because both of these return early.
Can I use Buffer.compare directly as a sort comparator?
Yes. It returns -1, 0 or 1, which is exactly the contract Array.prototype.sort expects, so arr.sort(Buffer.compare) works with no wrapper function.
How does Buffer.compare order buffers of different lengths?
It compares bytes until one differs; if the shorter buffer is a prefix of the longer one, the shorter sorts first. Length is only the tie-breaker, never the primary key.
More examples
Sorting and deduplicating binary keys
Sorting first makes duplicates adjacent, so a single linear pass removes them without a hash map. The second block is the trap: uppercase letters have lower byte values, so 'Banana' sorts ahead of 'apple' — for human-facing ordering use Intl.Collator on the decoded strings instead.
const keys = ['delta', 'alpha', 'charlie', 'alpha', 'bravo']
.map((s) => Buffer.from(s));
keys.sort(Buffer.compare);
const unique = keys.filter((key, i) => i === 0 || Buffer.compare(key, keys[i - 1]) !== 0);
console.log(unique.map((k) => k.toString()));
// [ 'alpha', 'bravo', 'charlie', 'delta' ]
// Byte order is not alphabetical order for mixed case:
const mixed = ['apple', 'Banana', 'cherry'].map((s) => Buffer.from(s));
console.log(mixed.sort(Buffer.compare).map((b) => b.toString()));
// [ 'Banana', 'apple', 'cherry' ]Instance compare with ranges, and equals
The four-argument instance form compares sub-ranges directly, which avoids the subarray allocations you would otherwise need inside a parsing loop. buf.equals is the right call when you only care whether the bytes match.
const a = Buffer.from('ABCDEFGH');
const b = Buffer.from('..CDEF..');
// Compare a[2..6) against b[2..6) without allocating slices.
console.log(a.compare(b, 2, 6, 2, 6)); // 0
console.log(a.compare(b)); // 1 (whole buffers differ)
// Plain equality reads better than checking for 0.
console.log(a.equals(Buffer.from('ABCDEFGH'))); // true
console.log(a.equals(b)); // false
// Uint8Arrays work as arguments since v8.
console.log(Buffer.compare(Buffer.from([1]), new Uint8Array([2]))); // -1Why not to use it on secrets
timingSafeEqual always examines every byte, so an attacker cannot narrow a token down one byte at a time by measuring response latency. It throws when the lengths differ, which is why the length check comes first and short-circuits.
const crypto = require('node:crypto');
const expected = crypto.createHmac('sha256', 'k').update('payload').digest();
const provided = Buffer.from(process.argv[2] ?? '', 'hex');
// WRONG: returns early at the first differing byte, leaking a timing signal.
// if (Buffer.compare(expected, provided) === 0) { ... }
// RIGHT: constant time, but the lengths must match first.
const ok = provided.length === expected.length &&
crypto.timingSafeEqual(expected, provided);
console.log('signature valid:', ok);
Discussion