mrkeyoor.com_
Sat 08 Aug 20:59 UTC
npmUtilsupdated 08 Aug 2026

shallow-equal

shallow-equal is a dependency-free TypeScript utility with three named functions: shallowEqualArrays compares array length and each element with ===, shallowEqualObjects compares own enumerable string keys and each value with ===, and shallowEqual chooses between those paths after checking whether both inputs are arrays. It is useful for memoization and change detection when nested values are intentionally treated by reference, but it is not a structural or deep equality check.

Verdict

A clear and very small helper when your contract is exactly shallow === comparison over arrays or plain records. Walk away when values include special objects, symbols, NaN, mutable nested data, or any requirement that sounds like structural equality.

API stability5/5The current API is three named functions with behavior that maps directly to short loops over Object.keys or array indexes. Version 3.1.0 tests reference identity, empty collections, key counts, mixed array and object inputs, nullish values, and nested references. There are no options or plugin hooks to churn, although unspoken special-object behavior remains part of the practical contract.
Docs3/5The README is brief but accurate: it distinguishes array, object, and generic forms, shows that equal-looking nested objects compare false, and warns that generic runtime type detection has a cost. It omits important JavaScript edge cases including NaN, signed zero, sparse arrays, symbols, prototypes, Dates, Maps, Sets, and the lack of a default export. Source and tests are needed for those answers.
Maintenance3/5The package is not archived or deprecated, and 3.1.0 was released alongside the repository's last push in February 2023. Its source is tiny, dependency-free, linted, and tested with a declared 100 percent coverage threshold, which lowers maintenance demand. There has been no visible repository activity for more than three years, so new runtime and packaging issues may not receive quick attention.
Ecosystem4/5shallow-equal recorded 4,229,028 npm downloads from July 31 through August 6, 2026 and provides outputs usable by CommonJS, modern bundlers, and TypeScript. Its plain boolean API fits memoization libraries and application change detection without adapters. The project itself has 78 GitHub stars and no plugin layer, so ecosystem strength is distribution reach rather than community extensions.

Use it if

  • You need a tiny, typed shallow comparator for arrays or plain record-like objects and want no runtime dependencies
  • Your data follows immutable-update rules, so a changed nested value also receives a new reference
  • You want separate specialized array and object functions to avoid the generic comparator's runtime Array.isArray checks
  • You need both ESM-aware and CommonJS bundler entry points from the same package
Skip it if

Setup reality

Install with npm install shallow-equal. Version 3.1.0 has no runtime dependencies, native code, peer dependencies, configuration, credentials, or environment variables. It publishes a CommonJS main file, a modern .mjs module hint, and bundled TypeScript declarations. Import its named exports: shallowEqual, shallowEqualArrays, or shallowEqualObjects. There is no default export from the package index. The specialized functions also make the intended input shape clearer and avoid the generic function's two Array.isArray calls. The important setup work is agreeing on data semantics. Every nested array, object, and function is compared by reference with ===, so in-place mutation can be missed when the top-level references stay the same. Conversely, recreating an equal nested object makes the comparison fail. Object comparison uses own enumerable string keys only and deliberately ignores order; array comparison is positional and treats a sparse hole like an explicit undefined at that index because direct indexing returns undefined. null and undefined compare equal only to themselves, while one falsy input against a different value returns false. The library does not support a custom comparator, ignored-key list, cycle traversal, or special handling for Dates, Maps, Sets, React elements, NaN, and signed zero. TypeScript types narrow inputs to records, arrays, null, or undefined, but they cannot ensure that a record contains only plain data. Add focused tests around your actual values before putting this comparator on a caching, rendering, or persistence boundary.

Patterns

Compare array elements shallowlycompare-arrays

import { shallowEqualArrays } from 'shallow-equal'

shallowEqualArrays([1, 2, 3], [1, 2, 3]) // true
shallowEqualArrays([{ id: 1 }], [{ id: 1 }]) // false

Elements use ===. Separately allocated objects are different even when their properties match.

Compare own enumerable object fieldscompare-objects

import { shallowEqualObjects } from 'shallow-equal'

shallowEqualObjects(
  { page: 2, query: 'books' },
  { query: 'books', page: 2 },
) // true

Object key order does not matter. Symbol, inherited, and non-enumerable properties are not part of the comparison.

Choose array or object comparison at runtimecompare-generic

import { shallowEqual } from 'shallow-equal'

shallowEqual(['a', 'b'], ['a', 'b']) // true
shallowEqual({ 0: 'a', 1: 'b' }, ['a', 'b']) // false

The generic function first checks both inputs with Array.isArray. Use a specialized function when the collection kind is already known.

Compare immutable records with shared nested valuesreuse-nested-references

const filters = { status: 'open' }
const before = { page: 1, filters }
const same = { page: 1, filters }
const changed = { page: 1, filters: { status: 'open' } }

shallowEqualObjects(before, same) // true
shallowEqualObjects(before, changed) // false

This works well with immutable updates: preserve references for unchanged nested values and allocate new ones for changes.

Cache one result by a shallow argument listmemoize-one-entry

import { shallowEqualArrays } from 'shallow-equal'

let previousArgs
let previousResult
function total(...numbers) {
  if (previousArgs && shallowEqualArrays(previousArgs, numbers)) return previousResult
  previousArgs = numbers
  previousResult = numbers.reduce((sum, value) => sum + value, 0)
  return previousResult
}

Copy or replace an argument array before later mutation. Holding a mutable array reference can make a cache report a false match.

Use an explicit React memo comparatorcompare-react-props

import { memo } from 'react'
import { shallowEqualObjects } from 'shallow-equal'

const Row = memo(
  function Row(props) {
    return <span>{props.label}</span>
  },
  (previous, next) => shallowEqualObjects(previous, next),
)

React.memo already shallow-compares props with Object.is by default. This changes edge behavior for NaN and signed zero, so add it only for a deliberate reason.

Compare optional recordshandle-nullish

shallowEqualObjects(undefined, undefined) // true
shallowEqualObjects(null, null)           // true
shallowEqualObjects(null, {})             // false
shallowEqualObjects(undefined, null)      // false

The TypeScript signature accepts null and undefined. Only identical nullish values pass the initial === check.

Account for strict-equality number edge casesrecognize-nan-semantics

shallowEqualArrays([NaN], [NaN]) // false
shallowEqualArrays([0], [-0])     // true

// Normalize first if your domain needs different semantics.
const normalize = (value) => Number.isNaN(value) ? 'NaN' : value

The implementation uses === rather than Object.is. This differs from React's default prop comparator and from some deep-equality libraries.

Do not compare Dates or Maps as plain objectsavoid-special-objects

shallowEqualObjects(new Date(0), new Date(86400000)) // true
shallowEqualObjects(new Map([['a', 1]]), new Map([['b', 2]])) // true

const sameInstant = firstDate.getTime() === secondDate.getTime()

Date and Map contents are not enumerable own string keys. Use type-specific comparison or a deep comparator that documents support for them.

Distinguish a missing key from undefineddetect-key-presence

shallowEqualObjects({ value: undefined }, { other: undefined }) // false
shallowEqualObjects({ value: undefined }, { value: undefined }) // true

The function checks both equal values and hasOwnProperty on the second object, so a different key with the same undefined value does not pass.

Normalize sparse arrays when holes matterunderstand-sparse-arrays

const sparse = new Array(1)
const explicit = [undefined]
shallowEqualArrays(sparse, explicit) // true

const normalized = Array.from(sparse, (value, index) =>
  index in sparse ? value : '<hole>'
)

The comparator checks length and indexed values, not property presence, so an array hole and explicit undefined are treated as equal.

Lock the intended reference contract in teststest-shallow-contract

const shared = { role: 'admin' }
expect(shallowEqualObjects(
  { user: shared, active: true },
  { user: shared, active: true },
)).toBe(true)
expect(shallowEqualObjects(
  { user: shared },
  { user: { role: 'admin' } },
)).toBe(false)

Test both shared and recreated nested references. Otherwise a later refactor from immutable to in-place updates can invalidate the comparison assumption.

Alternatives

PackageRegistryPick it when
fast-shallow-equalnpmUse when you want another small shallow object comparator and its narrower API matches your record-only hot path
dequalnpmUse when nested structure and built-in values such as Date, RegExp, Map, Set, and typed arrays need deep comparison
fast-deep-equalnpmUse when the real requirement is fast deep equality, with a React-specific build available for React element comparisons