A blazing fast equality comparison, either shallow or deep
TypeScript
612
344 commits
updated Oct 8, 2026
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 ~2.46kB when minified and gzipped.
The following types are handled out-of-the-box:
react elements and Arguments)ArrayBuffer / TypedArray / DataView instancesDate objectsRegExp objectsMap / Set iterablesPromise objects and then-ablesError objects, including subclasses and their own propertiesURL objectsnew Boolean() / new Number() / new String(), and boxed BigInt)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:
fast-equals/dist/es/index.mjsfast-equals/dist/cjs/index.cjsIf you are having any problems, want to request a new feature, or have any questions, please file an issue.
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.
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
MapsMap objects support complex keys (objects, Arrays, etc.), however
the spec for key lookups in Map are based on SameValueZero.
If the spec were followed for comparison, the following would always be false:
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 value
pairs. Therefore, the above would be true.
Performs a shallow equality comparison on the two objects passed and returns a boolean representing the value equivalency of the objects.
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)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)import { sameValueZeroEqual } from 'fast-equals';
const mainObject = { foo: NaN, bar: 'baz' };
const objectA = 'baz';
const objectB = NaN;
const objectC = { foo: NaN, bar: 'baz' };
console.log(sameValueZeroEqual(mainObject.bar, objectA)); // true
console.log(sameValueZeroEqual(mainObject.foo, objectB)); // true
console.log(sameValueZeroEqual(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)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(strictEqual(mainObject.bar, objectA)); // true
console.log(strictEqual(mainObject.foo, objectB)); // false
console.log(strictEqual(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.
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.
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. This includes:
Map / Set objectsconst 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. This includes:
Map / Set objectsconst array = ['foo'];
const otherArray = ['foo'];
array.bar = 'baz';
otherArray.bar = 'baz';
console.log(strictShallowEqual(array, otherArray)); // true
console.log(strictShallowEqual(array, ['foo'])); // false
Performs the same comparison as circularDeepEqual but performs a strict comparison of the objects. This includes:
Symbol properties on the objectMap / Set objectsfunction 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(strictCircularDeepEqual(first, second)); // true
console.log(strictCircularDeepEqual(first, new Circular('foo'))); // false
Performs the same comparison as circularShallowEqual but performs a strict comparison of the objects. This includes:
Map / Set objectsconst array = ['foo'];
const otherArray = ['foo'];
array.push(array);
otherArray.push(array); // Shallow comparison requires the same nested reference.
array.bar = 'baz';
otherArray.bar = 'baz';
console.log(strictCircularShallowEqual(array, otherArray)); // true
console.log(strictCircularShallowEqual(array, ['foo', array])); // false
Creates a custom equality comparator that will be used on nested values in the object. Unlike deepEqual and
shallowEqual, 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:
interface Cache<Key extends object, Value> {
delete(key: Key): boolean;
get(key: Key): Value | undefined;
set(key: Key, value: any): any;
}
interface ComparatorConfig<Meta> {
areArrayBuffersEqual: EqualityComparator<Meta>;
areArraysEqual: EqualityComparator<Meta>;
areDataViewsEqual: EqualityComparator<Meta>;
areDatesEqual: EqualityComparator<Meta>;
areErrorsEqual: EqualityComparator<Meta>;
areFunctionsEqual: EqualityComparator<Meta>;
areMapsEqual: EqualityComparator<Meta>;
areNumbersEqual: EqualityComparator<Meta>;
areObjectsEqual: EqualityComparator<Meta>;
arePrimitiveWrappersEqual: EqualityComparator<Meta>;
areRegExpsEqual: EqualityComparator<Meta>;
areSetsEqual: EqualityComparator<Meta>;
areTypedArraysEqual: EqualityComparator<Meta>;
areUrlsEqual: EqualityComparator<Meta>;
getUnsupportedCustomComparator:
(<Type>(a: Type, b: Type, state: State<Meta>, tag: string) => EqualityComparator<Meta> | undefined) | undefined;
strictNullPrototypeComparison?: boolean;
}
function createCustomEqual<Meta>(options: {
circular?: boolean;
createCustomConfig?: (defaultConfig: ComparatorConfig<Meta>) => Partial<ComparatorConfig<Meta>>;
createInternalComparator?: (
compare: <A, B>(a: A, b: B, state: State<Meta>) => boolean,
) => (a: any, b: any, indexOrKeyA: any, indexOrKeyB: any, parentA: any, parentB: any, state: State<Meta>) => boolean;
createState?: () => { cache?: Cache; meta?: Meta };
strict?: boolean;
strictNullPrototypeComparison?: boolean;
}): <A, B>(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 comparing
equality of the keys, the iteration index is provided as both indexOrKeyA and indexOrKeyB to help use-cases where
ordering of keys matters to equality.
Defaults to true, which keeps a null-prototype object (Object.create(null)) unequal to an otherwise identical plain
object. Set it to false to compare the two as dictionaries, for values such as
ProseMirror node attributes or Object.groupBy results.
const equal = createCustomEqual({ strictNullPrototypeComparison: false });
const attrs = Object.assign(Object.create(null), { aspectRatio: 16 / 9 });
equal(attrs, { ...attrs }); // true
This is the only exception to the constructor check: a null-prototype object remains unequal to arrays, Maps, dates,
and other built-ins, and distinct class constructors remain unequal to each other. It applies to nested values and can
be combined with circular, strict, and custom comparators.
If you want to compare objects whose tag is not handled out-of-the-box (such as a custom Symbol.toStringTag or a
WeakMap), you can provide a getUnsupportedCustomComparator method. It receives both values, the comparison state,
and the tag, and returns the comparator to use for them. Returning undefined treats the values as unequal. See
this recipe for an example.
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.
RegExp comparatorsmeta in comparisonAll benchmarks were performed on an i9-11900H Ubuntu Linux 24.04 laptop with 64GB of memory using NodeJS version
24.15.0, and are based on averages of running comparisons based deep equality on the following object types:
String, Number, null, undefined)FunctionObjectArrayDateRegExpreact elementsTesting mixed objects equal...
┌────────────────────────────────────────┬────────────────┐
│ Name │ Ops / sec │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (passed) │ 1522774.1567 │
├────────────────────────────────────────┼────────────────┤
│ react-fast-compare (passed) │ 1285289.880042 │
├────────────────────────────────────────┼────────────────┤
│ fast-deep-equal (passed) │ 1285105.414715 │
├────────────────────────────────────────┼────────────────┤
│ shallow-equal-fuzzy (passed) │ 1248325.039481 │
├────────────────────────────────────────┼────────────────┤
│ nano-equal (failed) │ 962152.688616 │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (circular) (passed) │ 800100.693495 │
├────────────────────────────────────────┼────────────────┤
│ dequal/lite (passed) │ 778011.650869 │
├────────────────────────────────────────┼────────────────┤
│ dequal (passed) │ 770187.208947 │
├────────────────────────────────────────┼────────────────┤
│ underscore.isEqual (passed) │ 449433.41171 │
├────────────────────────────────────────┼────────────────┤
│ assert.deepStrictEqual (passed) │ 442249.829162 │
├────────────────────────────────────────┼────────────────┤
│ es-toolkit (passed) │ 355943.82757 │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (strict) (passed) │ 307931.96677 │
├────────────────────────────────────────┼────────────────┤
│ lodash.isEqual (passed) │ 294660.269815 │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (strict circular) (passed) │ 252760.794458 │
├────────────────────────────────────────┼────────────────┤
│ deep-eql (passed) │ 166131.383227 │
├────────────────────────────────────────┼────────────────┤
│ deep-equal (passed) │ 970.944208 │
└────────────────────────────────────────┴────────────────┘
Testing mixed objects not equal...
┌────────────────────────────────────────┬────────────────┐
│ Name │ Ops / sec │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (passed) │ 5122472.769802 │
├────────────────────────────────────────┼────────────────┤
│ fast-deep-equal (passed) │ 3520297.242957 │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (circular) (passed) │ 3485912.9348 │
├────────────────────────────────────────┼────────────────┤
│ react-fast-compare (passed) │ 3414203.488651 │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (strict) (passed) │ 3038182.032793 │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (strict circular) (passed) │ 2344879.08551 │
├────────────────────────────────────────┼────────────────┤
│ dequal/lite (passed) │ 1340883.333611 │
├────────────────────────────────────────┼────────────────┤
│ dequal (passed) │ 1311832.728093 │
├────────────────────────────────────────┼────────────────┤
│ shallow-equal-fuzzy (failed) │ 1249967.301358 │
├────────────────────────────────────────┼────────────────┤
│ nano-equal (passed) │ 1096529.56672 │
├────────────────────────────────────────┼────────────────┤
│ underscore.isEqual (passed) │ 660524.165237 │
├────────────────────────────────────────┼────────────────┤
│ lodash.isEqual (passed) │ 385500.174796 │
├────────────────────────────────────────┼────────────────┤
│ es-toolkit (passed) │ 365617.977684 │
├────────────────────────────────────────┼────────────────┤
│ deep-eql (passed) │ 186388.440331 │
├────────────────────────────────────────┼────────────────┤
│ assert.deepStrictEqual (passed) │ 21060.162935 │
├────────────────────────────────────────┼────────────────┤
│ deep-equal (passed) │ 3819.218852 │
└────────────────────────────────────────┴────────────────┘
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-eql
fully supported their comparisonfast-deep-equal, react-fast-compare and nano-equal throw on objects with null as prototype
(Object.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 uses
SameValue 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 SameValue
equality for datesreact-fast-compare does not support NaN or SameValue equality for dates, and does not compare function
equalityshallow-equal-fuzzy does not strictly compare object type or regexp values, nor SameValue equality for datesunderscore.isEqual does not support SameValue equality for primitives or datesAll of these have the potential of inflating the respective library's numbers in comparison to fast-equals, but it was
the closest apples-to-apples comparison I could create of a reasonable sample size. It should be noted that react
elements can be circular objects, however simple elements are not; I kept the react comparison very basic to allow it
to be included.
Overall
Maintained
Code review
npm
Latest release
Releases this year
5,317 followers · starred Aug 2025
426 followers · starred Sep 2026
131 followers · starred Nov 2025
116 followers · starred Apr 2019
A blazing fast equality comparison, either shallow or deep
TypeScript
612
344 commits
updated Oct 8, 2026
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 ~2.46kB when minified and gzipped.
The following types are handled out-of-the-box:
react elements and Arguments)ArrayBuffer / TypedArray / DataView instancesDate objectsRegExp objectsMap / Set iterablesPromise objects and then-ablesError objects, including subclasses and their own propertiesURL objectsnew Boolean() / new Number() / new String(), and boxed BigInt)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:
fast-equals/dist/es/index.mjsfast-equals/dist/cjs/index.cjsIf you are having any problems, want to request a new feature, or have any questions, please file an issue.
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.
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
MapsMap objects support complex keys (objects, Arrays, etc.), however
the spec for key lookups in Map are based on SameValueZero.
If the spec were followed for comparison, the following would always be false:
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 value
pairs. Therefore, the above would be true.
Performs a shallow equality comparison on the two objects passed and returns a boolean representing the value equivalency of the objects.
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)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)import { sameValueZeroEqual } from 'fast-equals';
const mainObject = { foo: NaN, bar: 'baz' };
const objectA = 'baz';
const objectB = NaN;
const objectC = { foo: NaN, bar: 'baz' };
console.log(sameValueZeroEqual(mainObject.bar, objectA)); // true
console.log(sameValueZeroEqual(mainObject.foo, objectB)); // true
console.log(sameValueZeroEqual(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)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(strictEqual(mainObject.bar, objectA)); // true
console.log(strictEqual(mainObject.foo, objectB)); // false
console.log(strictEqual(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.
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.
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. This includes:
Map / Set objectsconst 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. This includes:
Map / Set objectsconst array = ['foo'];
const otherArray = ['foo'];
array.bar = 'baz';
otherArray.bar = 'baz';
console.log(strictShallowEqual(array, otherArray)); // true
console.log(strictShallowEqual(array, ['foo'])); // false
Performs the same comparison as circularDeepEqual but performs a strict comparison of the objects. This includes:
Symbol properties on the objectMap / Set objectsfunction 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(strictCircularDeepEqual(first, second)); // true
console.log(strictCircularDeepEqual(first, new Circular('foo'))); // false
Performs the same comparison as circularShallowEqual but performs a strict comparison of the objects. This includes:
Map / Set objectsconst array = ['foo'];
const otherArray = ['foo'];
array.push(array);
otherArray.push(array); // Shallow comparison requires the same nested reference.
array.bar = 'baz';
otherArray.bar = 'baz';
console.log(strictCircularShallowEqual(array, otherArray)); // true
console.log(strictCircularShallowEqual(array, ['foo', array])); // false
Creates a custom equality comparator that will be used on nested values in the object. Unlike deepEqual and
shallowEqual, 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:
interface Cache<Key extends object, Value> {
delete(key: Key): boolean;
get(key: Key): Value | undefined;
set(key: Key, value: any): any;
}
interface ComparatorConfig<Meta> {
areArrayBuffersEqual: EqualityComparator<Meta>;
areArraysEqual: EqualityComparator<Meta>;
areDataViewsEqual: EqualityComparator<Meta>;
areDatesEqual: EqualityComparator<Meta>;
areErrorsEqual: EqualityComparator<Meta>;
areFunctionsEqual: EqualityComparator<Meta>;
areMapsEqual: EqualityComparator<Meta>;
areNumbersEqual: EqualityComparator<Meta>;
areObjectsEqual: EqualityComparator<Meta>;
arePrimitiveWrappersEqual: EqualityComparator<Meta>;
areRegExpsEqual: EqualityComparator<Meta>;
areSetsEqual: EqualityComparator<Meta>;
areTypedArraysEqual: EqualityComparator<Meta>;
areUrlsEqual: EqualityComparator<Meta>;
getUnsupportedCustomComparator:
(<Type>(a: Type, b: Type, state: State<Meta>, tag: string) => EqualityComparator<Meta> | undefined) | undefined;
strictNullPrototypeComparison?: boolean;
}
function createCustomEqual<Meta>(options: {
circular?: boolean;
createCustomConfig?: (defaultConfig: ComparatorConfig<Meta>) => Partial<ComparatorConfig<Meta>>;
createInternalComparator?: (
compare: <A, B>(a: A, b: B, state: State<Meta>) => boolean,
) => (a: any, b: any, indexOrKeyA: any, indexOrKeyB: any, parentA: any, parentB: any, state: State<Meta>) => boolean;
createState?: () => { cache?: Cache; meta?: Meta };
strict?: boolean;
strictNullPrototypeComparison?: boolean;
}): <A, B>(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 comparing
equality of the keys, the iteration index is provided as both indexOrKeyA and indexOrKeyB to help use-cases where
ordering of keys matters to equality.
Defaults to true, which keeps a null-prototype object (Object.create(null)) unequal to an otherwise identical plain
object. Set it to false to compare the two as dictionaries, for values such as
ProseMirror node attributes or Object.groupBy results.
const equal = createCustomEqual({ strictNullPrototypeComparison: false });
const attrs = Object.assign(Object.create(null), { aspectRatio: 16 / 9 });
equal(attrs, { ...attrs }); // true
This is the only exception to the constructor check: a null-prototype object remains unequal to arrays, Maps, dates,
and other built-ins, and distinct class constructors remain unequal to each other. It applies to nested values and can
be combined with circular, strict, and custom comparators.
If you want to compare objects whose tag is not handled out-of-the-box (such as a custom Symbol.toStringTag or a
WeakMap), you can provide a getUnsupportedCustomComparator method. It receives both values, the comparison state,
and the tag, and returns the comparator to use for them. Returning undefined treats the values as unequal. See
this recipe for an example.
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.
RegExp comparatorsmeta in comparisonAll benchmarks were performed on an i9-11900H Ubuntu Linux 24.04 laptop with 64GB of memory using NodeJS version
24.15.0, and are based on averages of running comparisons based deep equality on the following object types:
String, Number, null, undefined)FunctionObjectArrayDateRegExpreact elementsTesting mixed objects equal...
┌────────────────────────────────────────┬────────────────┐
│ Name │ Ops / sec │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (passed) │ 1522774.1567 │
├────────────────────────────────────────┼────────────────┤
│ react-fast-compare (passed) │ 1285289.880042 │
├────────────────────────────────────────┼────────────────┤
│ fast-deep-equal (passed) │ 1285105.414715 │
├────────────────────────────────────────┼────────────────┤
│ shallow-equal-fuzzy (passed) │ 1248325.039481 │
├────────────────────────────────────────┼────────────────┤
│ nano-equal (failed) │ 962152.688616 │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (circular) (passed) │ 800100.693495 │
├────────────────────────────────────────┼────────────────┤
│ dequal/lite (passed) │ 778011.650869 │
├────────────────────────────────────────┼────────────────┤
│ dequal (passed) │ 770187.208947 │
├────────────────────────────────────────┼────────────────┤
│ underscore.isEqual (passed) │ 449433.41171 │
├────────────────────────────────────────┼────────────────┤
│ assert.deepStrictEqual (passed) │ 442249.829162 │
├────────────────────────────────────────┼────────────────┤
│ es-toolkit (passed) │ 355943.82757 │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (strict) (passed) │ 307931.96677 │
├────────────────────────────────────────┼────────────────┤
│ lodash.isEqual (passed) │ 294660.269815 │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (strict circular) (passed) │ 252760.794458 │
├────────────────────────────────────────┼────────────────┤
│ deep-eql (passed) │ 166131.383227 │
├────────────────────────────────────────┼────────────────┤
│ deep-equal (passed) │ 970.944208 │
└────────────────────────────────────────┴────────────────┘
Testing mixed objects not equal...
┌────────────────────────────────────────┬────────────────┐
│ Name │ Ops / sec │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (passed) │ 5122472.769802 │
├────────────────────────────────────────┼────────────────┤
│ fast-deep-equal (passed) │ 3520297.242957 │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (circular) (passed) │ 3485912.9348 │
├────────────────────────────────────────┼────────────────┤
│ react-fast-compare (passed) │ 3414203.488651 │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (strict) (passed) │ 3038182.032793 │
├────────────────────────────────────────┼────────────────┤
│ fast-equals (strict circular) (passed) │ 2344879.08551 │
├────────────────────────────────────────┼────────────────┤
│ dequal/lite (passed) │ 1340883.333611 │
├────────────────────────────────────────┼────────────────┤
│ dequal (passed) │ 1311832.728093 │
├────────────────────────────────────────┼────────────────┤
│ shallow-equal-fuzzy (failed) │ 1249967.301358 │
├────────────────────────────────────────┼────────────────┤
│ nano-equal (passed) │ 1096529.56672 │
├────────────────────────────────────────┼────────────────┤
│ underscore.isEqual (passed) │ 660524.165237 │
├────────────────────────────────────────┼────────────────┤
│ lodash.isEqual (passed) │ 385500.174796 │
├────────────────────────────────────────┼────────────────┤
│ es-toolkit (passed) │ 365617.977684 │
├────────────────────────────────────────┼────────────────┤
│ deep-eql (passed) │ 186388.440331 │
├────────────────────────────────────────┼────────────────┤
│ assert.deepStrictEqual (passed) │ 21060.162935 │
├────────────────────────────────────────┼────────────────┤
│ deep-equal (passed) │ 3819.218852 │
└────────────────────────────────────────┴────────────────┘
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-eql
fully supported their comparisonfast-deep-equal, react-fast-compare and nano-equal throw on objects with null as prototype
(Object.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 uses
SameValue 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 SameValue
equality for datesreact-fast-compare does not support NaN or SameValue equality for dates, and does not compare function
equalityshallow-equal-fuzzy does not strictly compare object type or regexp values, nor SameValue equality for datesunderscore.isEqual does not support SameValue equality for primitives or datesAll of these have the potential of inflating the respective library's numbers in comparison to fast-equals, but it was
the closest apples-to-apples comparison I could create of a reasonable sample size. It should be noted that react
elements can be circular objects, however simple elements are not; I kept the react comparison very basic to allow it
to be included.
Overall
Maintained
Code review
npm
Latest release
Releases this year
5,317 followers · starred Aug 2025
426 followers · starred Sep 2026
131 followers · starred Nov 2025
116 followers · starred Apr 2019