A blazing-fast equality comparison utility for a variety of use-cases
npm install fast-equals> fast-equals
Perform blazing fast equality comparisons between two objects, while also allowing for flexibility for
various use-cases. It has no dependencies, and is ~2kB when minified and gzipped.
The following types are handled out-of-the-box:
- Plain objects (including react elements and Arguments)
- Arrays
- ArrayBuffer / TypedArray / DataView instances
- Date objects
- RegExp objects
- Map / Set iterables
- Promise objects and then-ables
- Primitive wrappers (new Boolean() / new Number() / new String())
- Custom class instances, including subclasses of native classes
Methods are available for deep, shallow, SameValue,SameValueZero, or
strict equality comparison. In addition, you
can opt into support for circular objects, or performing a "strict" comparison with unconventional property definition,
or both. You can also customize any specific type comparison based on your application's use-cases.
By default, npm should resolve the correct build of the package based on your consumption (ESM vs CommonJS). However, if
you want to force use of a specific build, they can be located here:
- ESM => fast-equals/dist/es/index.mjs
- CommonJS => fast-equals/dist/cjs/index.cjs
If you are having any problems, want to request a new feature, or have any questions,
please file an issue.
- Usage
- Available methods
- deepEqual
- Comparing Maps
- shallowEqual
- sameValueEqual
- sameValueZeroEqual
- strictEqual
- circularDeepEqual
- circularShallowEqual
- strictDeepEqual
- strictShallowEqual
- strictCircularDeepEqual
- strictCircularShallowEqual
- createCustomEqual
- getUnsupportedCustomComparator
- Recipes
- Benchmarks
``ts
import { deepEqual } from 'fast-equals';
console.log(deepEqual({ foo: 'bar' }, { foo: 'bar' })); // true
`
Performs a deep equality comparison on the two objects passed and returns a boolean representing the value equivalency
of the objects.
`ts
import { deepEqual } from 'fast-equals';
const objectA = { foo: { bar: 'baz' } };
const objectB = { foo: { bar: 'baz' } };
console.log(objectA === objectB); // false
console.log(deepEqual(objectA, objectB)); // true
`
#### Comparing Maps
Map objects support complex keys (objects, Arrays, etc.), howeverMap
the spec for key lookups in are based on SameZeroValue.false
If the spec were followed for comparison, the following would always be :
`ts
const mapA = new Map([[{ foo: 'bar' }, { baz: 'quz' }]]);
const mapB = new Map([[{ foo: 'bar' }, { baz: 'quz' }]]);
deepEqual(mapA, mapB);
`
To support true deep equality of all contents, fast-equals will perform a deep equality comparison for key and valuetrue
pairs. Therefore, the above would be .
Performs a shallow equality comparison on the two objects passed and returns a boolean representing the value
equivalency of the objects.
`ts
import { shallowEqual } from 'fast-equals';
const nestedObject = { bar: 'baz' };
const objectA = { foo: nestedObject };
const objectB = { foo: nestedObject };
const objectC = { foo: { bar: 'baz' } };
console.log(objectA === objectB); // false
console.log(shallowEqual(objectA, objectB)); // true
console.log(shallowEqual(objectA, objectC)); // false
`
Performs a SameValue comparison on the two objects passed
and returns a boolean representing the value equivalency of the objects. In simple terms, this means:
- +0 and -0 are not equalNaN
- is equal to NaNa === b
- All other items are based on referential equality ()
`ts
import { sameValueEqual } from 'fast-equals';
const mainObject = { foo: NaN, bar: 'baz' };
const objectA = 'baz';
const objectB = NaN;
const objectC = { foo: NaN, bar: 'baz' };
console.log(sameValueEqual(mainObject.bar, objectA)); // true
console.log(sameValueEqual(mainObject.foo, objectB)); // true
console.log(sameValueEqual(mainObject, objectC)); // false
`
_NOTE: In environments that support
Object.is,
sameValueEqual is just a re-export of that method._
Performs a SameValueZero comparison on the two
objects passed and returns a boolean representing the value equivalency of the objects. In simple terms, this means:
- +0 and -0 are equalNaN
- is equal to NaNa === b
- All other items are based on referential equality ()
`ts
import { sameValueEqual } from 'fast-equals';
const mainObject = { foo: NaN, bar: 'baz' };
const objectA = 'baz';
const objectB = NaN;
const objectC = { foo: NaN, bar: 'baz' };
console.log(sameValueEqual(mainObject.bar, objectA)); // true
console.log(sameValueEqual(mainObject.foo, objectB)); // true
console.log(sameValueEqual(mainObject, objectC)); // false
`
Performs a Strict Equality comparison on the
two objects passed and returns a boolean representing the referential equality of the objects. In simple terms, this
means:
- +0 and -0 are equalNaN
- is not equal to NaNa === b
- All other items are based on referential equality ()
`ts
import { strictEqual } from 'fast-equals';
const mainObject = { foo: NaN, bar: 'baz' };
const objectA = 'baz';
const objectB = NaN;
const objectC = { foo: NaN, bar: 'baz' };
console.log(sameValueEqual(mainObject.bar, objectA)); // true
console.log(sameValueEqual(mainObject.foo, objectB)); // false
console.log(sameValueEqual(mainObject, objectC)); // false
`
_NOTE: This is mainly a convenience function, such as needing a default functional equality comparator. Naturally,
it is faster to simply compare a === b. :)_
Performs the same comparison as deepEqual but supports circular objects. It is slower than deepEqual, so only use if
you know circular objects are present.
`ts
function Circular(value) {
this.me = {
deeply: {
nested: {
reference: this,
},
},
value,
};
}
console.log(circularDeepEqual(new Circular('foo'), new Circular('foo'))); // true
console.log(circularDeepEqual(new Circular('foo'), new Circular('bar'))); // false
`
Just as with deepEqual, both keys and values are compared for deep equality.
Performs the same comparison as shallowequal but supports circular objects. It is slower than shallowEqual, so only
use if you know circular objects are present.
`ts
const array = ['foo'];
array.push(array);
console.log(circularShallowEqual(array, ['foo', array])); // true
console.log(circularShallowEqual(array, [array])); // false
`
Performs the same comparison as deepEqual but performs a strict comparison of the objects. In this includes:
- Checking symbol properties
- Checking non-enumerable properties in object comparisons
- Checking full descriptor of properties on the object to match
- Checking non-index properties on arrays
- Checking non-key properties on Map / Set objects
`ts
const array = [{ foo: 'bar' }];
const otherArray = [{ foo: 'bar' }];
array.bar = 'baz';
otherArray.bar = 'baz';
console.log(strictDeepEqual(array, otherArray)); // true;
console.log(strictDeepEqual(array, [{ foo: 'bar' }])); // false;
`
Performs the same comparison as shallowEqual but performs a strict comparison of the objects. In this includes:
- Checking non-enumerable properties in object comparisons
- Checking full descriptor of properties on the object to match
- Checking non-index properties on arrays
- Checking non-key properties on Map / Set objects
`ts
const array = ['foo'];
const otherArray = ['foo'];
array.bar = 'baz';
otherArray.bar = 'baz';
console.log(strictDeepEqual(array, otherArray)); // true;
console.log(strictDeepEqual(array, ['foo'])); // false;
`
Performs the same comparison as circularDeepEqual but performs a strict comparison of the objects. In this includes:
- Checking Symbol properties on the objectMap
- Checking non-enumerable properties in object comparisons
- Checking full descriptor of properties on the object to match
- Checking non-index properties on arrays
- Checking non-key properties on / Set objects
`ts
function Circular(value) {
this.me = {
deeply: {
nested: {
reference: this,
},
},
value,
};
}
const first = new Circular('foo');
Object.defineProperty(first, 'bar', {
enumerable: false,
value: 'baz',
});
const second = new Circular('foo');
Object.defineProperty(second, 'bar', {
enumerable: false,
value: 'baz',
});
console.log(circularDeepEqual(first, second)); // true
console.log(circularDeepEqual(first, new Circular('foo'))); // false
`
Performs the same comparison as circularShallowEqual but performs a strict comparison of the objects. In this
includes:
- Checking non-enumerable properties in object comparisons
- Checking full descriptor of properties on the object to match
- Checking non-index properties on arrays
- Checking non-key properties on Map / Set objects
`ts
const array = ['foo'];
const otherArray = ['foo'];
array.push(array);
otherArray.push(otherArray);
array.bar = 'baz';
otherArray.bar = 'baz';
console.log(circularShallowEqual(array, otherArray)); // true
console.log(circularShallowEqual(array, ['foo', array])); // false
`
Creates a custom equality comparator that will be used on nested values in the object. Unlike deepEqual andshallowEqual, this is a factory method that receives the default options used internally, and allows you to override
the defaults as needed. This is generally for extreme edge-cases, or supporting legacy environments.
The signature is as follows:
`ts
interface Cache
delete(key: Key): boolean;
get(key: Key): Value | undefined;
set(key: Key, value: any): any;
}
interface ComparatorConfig {
areArrayBuffersEqual: EqualityComparator;
areArraysEqual: EqualityComparator;
areDataViewsEqual: EqualityComparator;
areDatesEqual: EqualityComparator;
areErrorsEqual: EqualityComparator;
areFunctionsEqual: EqualityComparator;
areMapsEqual: EqualityComparator;
areNumbersEqual: EqualityComparator;
areObjectsEqual: EqualityComparator;
arePrimitiveWrappersEqual: EqualityComparator;
areRegExpsEqual: EqualityComparator;
areSetsEqual: EqualityComparator;
areTypedArraysEqual: EqualityComparator;
areUrlsEqual: EqualityComparator;
getUnsupportedCustomComparator:
}
function createCustomEqual(options: {
circular?: boolean;
createCustomConfig?: (defaultConfig: ComparatorConfig) => Partial
createInternalComparator?: (
compare: (a: A, b: B, state: State) => boolean,
) => (a: any, b: any, indexOrKeyA: any, indexOrKeyB: any, parentA: any, parentB: any, state: State) => boolean;
createState?: () => { cache?: Cache; meta?: Meta };
strict?: boolean;
}): (a: A, b: B) => boolean;
`
Create a custom equality comparator. This allows complete control over building a bespoke equality method, in case your
use-case requires a higher degree of performance, legacy environment support, or any other non-standard usage. The
recipes provide examples of use in different use-cases, but if you have a specific goal in mind and would
like assistance feel free to file an issue.
_NOTE: Map implementations compare equality for both keys and value. When using a custom comparator and comparingindexOrKeyA
equality of the keys, the iteration index is provided as both and indexOrKeyB to help use-cases where
ordering of keys matters to equality._
#### getUnsupportedCustomComparator
If you want to compare objects that have a custom @@toStringTag, you can provide a map of the custom tags you want togetUnsupportedCustomComparator
support via the option. See this recipe for an
example.
#### Recipes
Some recipes have been created to provide examples of use-cases for createCustomEqual. Even if not directly applicable
to the problem you are solving, they can offer guidance of how to structure your solution.
- Legacy environment support for RegExp comparators
- Explicit property check
- Using meta in comparison
- Comparing non-standard properties
- Strict property descriptor comparison
- Legacy environment support for circular equal comparators
- Custom comparator support
All benchmarks were performed on an i9-11900H Ubuntu Linux 24.04 laptop with 64GB of memory using NodeJS version
24.11.1, and are based on averages of running comparisons based deep equality on the following object types:
- Primitives (String, Number, null, undefined)Function
- Object
- Array
- Date
- RegExp
- react
- elements
- A mixed object with a combination of all the above types
`bash
┌────────────────────────────────────────┬────────────────┐
│ Name │ Ops / sec │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (passed) │ 1544237.29413 │
├────────────────────────────────────────┼────────────────┤
│ fast-deep-equal (passed) │ 1328583.767745 │
├────────────────────────────────────────┼────────────────┤
│ react-fast-compare (passed) │ 1301727.296375 │
├────────────────────────────────────────┼────────────────┤
│ shallow-equal-fuzzy (passed) │ 1225981.400919 │
├────────────────────────────────────────┼────────────────┤
│ nano-equal (failed) │ 969495.538753 │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (circular) (passed) │ 813716.49516 │
├────────────────────────────────────────┼────────────────┤
│ dequal/lite (passed) │ 780805.627339 │
├────────────────────────────────────────┼────────────────┤
│ dequal (passed) │ 767208.995048 │
├────────────────────────────────────────┼────────────────┤
│ underscore.isEqual (passed) │ 490695.830468 │
├────────────────────────────────────────┼────────────────┤
│ assert.deepStrictEqual (passed) │ 471011.425391 │
├────────────────────────────────────────┼────────────────┤
│ lodash.isEqual (passed) │ 296064.057382 │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (strict) (passed) │ 225894.800964 │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (strict circular) (passed) │ 195657.732354 │
├────────────────────────────────────────┼────────────────┤
│ deep-eql (passed) │ 162718.102328 │
├────────────────────────────────────────┼────────────────┤
│ deep-equal (passed) │ 954.172311 │
└────────────────────────────────────────┴────────────────┘
Testing mixed objects not equal...
┌────────────────────────────────────────┬────────────────┐
│ Name │ Ops / sec │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (passed) │ 5112341.000979 │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (circular) (passed) │ 3501225.300307 │
├────────────────────────────────────────┼────────────────┤
│ fast-deep-equal (passed) │ 3471838.735181 │
├────────────────────────────────────────┼────────────────┤
│ react-fast-compare (passed) │ 3439612.908273 │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (strict) (passed) │ 1797319.423491 │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (strict circular) (passed) │ 1534168.229167 │
├────────────────────────────────────────┼────────────────┤
│ dequal/lite (passed) │ 1357981.758571 │
├────────────────────────────────────────┼────────────────┤
│ dequal (passed) │ 1328078.173967 │
├────────────────────────────────────────┼────────────────┤
│ shallow-equal-fuzzy (failed) │ 1224747.272118 │
├────────────────────────────────────────┼────────────────┤
│ nano-equal (passed) │ 1087373.99615 │
├────────────────────────────────────────┼────────────────┤
│ underscore.isEqual (passed) │ 927298.592729 │
├────────────────────────────────────────┼────────────────┤
│ lodash.isEqual (passed) │ 387294.235476 │
├────────────────────────────────────────┼────────────────┤
│ deep-eql (passed) │ 186028.168827 │
├────────────────────────────────────────┼────────────────┤
│ assert.deepStrictEqual (passed) │ 21261.312424 │
├────────────────────────────────────────┼────────────────┤
│ deep-equal (passed) │ 3782.329948 │
└────────────────────────────────────────┴────────────────┘
`
Caveats that impact the benchmark (and accuracy of comparison):
- Maps, Promises, and Sets were excluded from the benchmark entirely because no library other than deep-eqlfast-deep-equal
fully supported their comparison
- , react-fast-compare and nano-equal throw on objects with null as prototypeObject.create(null)
()assert.deepStrictEqual
- does not support NaN or SameValue equality for datesdeep-eql
- does not support SameValue equality for zero equality (positive and negative zero are not equal)deep-equal
- does not support NaN and does not strictly compare object type, or date / regexp values, nor usesSameValue
equality for datesfast-deep-equal
- does not support NaN or SameValue equality for datesnano-equal
- does not strictly compare object property structure, array length, or object type, nor SameValuereact-fast-compare
equality for dates
- does not support NaN or SameValue equality for dates, and does not compare functionshallow-equal-fuzzy
equality
- does not strictly compare object type or regexp values, nor SameValue equality for datesunderscore.isEqual
- does not support SameValue equality for primitives or dates
All of these have the potential of inflating the respective library's numbers in comparison to fast-equals, but it wasreact
the closest apples-to-apples comparison I could create of a reasonable sample size. It should be noted that react` comparison very basic to allow it
elements can be circular objects, however simple elements are not; I kept the
to be included.