mrkeyoor.com_
Wed 23 Sept 00:33 UTC
npmUtilsupdated 21 Sept 2026

shallow-equal review

shallow-equal 3.1.0 compares one level of an array or plain record and returns a boolean. We measured a 0.7 KB minified, 0.4 KB gzipped browser bundle, with no runtime dependencies. The array function checks length and each indexed value with ===. The object function checks enumerable own string-keyed properties, their count and their values, also with ===. A generic function chooses between those two paths after Array.isArray checks. Version 3.1.0 corrected the package's modern `.mjs` reference; the comparison rules themselves stayed small and explicit.

Verdict

shallow-equal 3.1.0 added 0.4 KB gzipped and no dependencies in our sandbox, making it a cheap fit for immutable data whose top-level references carry meaning. Skip it for nested structures or built-in collection types, and note that the code has not moved since February 2023.

We installed it

Lab card: what happened when we installed shallow-equalScreenshot of shallow-equal documentation
Install✓ · 0.6s1 package on disk · 1 MB
ImportESM import works · require() works · CommonJS package
Browser0.4 KBgzipped (0.7 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does shallow-equal install cleanly?

Yes. In a fresh container with an empty cache, npm install shallow-equal finished in 0.6s, leaving 1 package and 1 MB on disk. npm audit reported no known vulnerabilities.

How much does shallow-equal add to a browser bundle?

0.4 KB gzipped (0.7 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does shallow-equal work with both ESM and CommonJS?

Yes. Both import 'shallow-equal' and require('shallow-equal') worked in Node 22 in our run. The package is published as CommonJS.

Does shallow-equal include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

shallow-equal or fast-shallow-equal: which should you use?

fast-shallow-equal: Choose it when benchmarked shallow object comparison is the only operation you need. shallow-equal 3.1.0 added 0.4 KB gzipped and no dependencies in our sandbox, making it a cheap fit for immutable data whose top-level references carry meaning.

When should you not use shallow-equal?

You need nested structural equality; separately created child objects fail because values are compared with ===

API stability5/5Version 3.1.0 exports exactly three named functions: one for arrays, one for objects and a generic dispatcher. Their source uses only Array.isArray, Object.keys, hasOwnProperty and strict equality, leaving little hidden behavior. The latest release fixed the modern module filename rather than changing comparison results. That narrow contract is easy to pin with tests and unlikely to surprise existing callers.
Docs3/5The README explains the three exports with examples and warns that the generic form pays for runtime type detection. The TypeScript declarations expose null and undefined inputs, and the small source answers most remaining questions. The guide does not document NaN, signed zero, sparse arrays, symbol keys, Date, Map or Set behavior, so users need to inspect implementation code or write probes for common edge cases.
Maintenance3/5The package is unarchived and npm does not mark version 3.1.0 deprecated. That release fixed the modern module reference on February 15, 2023, which is also the date of the last repository push. GitHub search currently finds 0 open issues, and the source enforces 100 percent coverage thresholds. Low code volume reduces upkeep, though more than 3 years without a push raises the likely wait for a new packaging or runtime problem.
Ecosystem4/5npm recorded 4,512,318 downloads between August 18 and 24, 2026, despite the repository having 78 stars and no plugin layer. CommonJS, a modern module file and bundled declarations let the package sit in many JavaScript and TypeScript builds. Its reach is mostly as a tiny utility inside other packages; it does not provide adapters, framework bindings or the broader type coverage of a deep comparator.

Use it if

  • You use immutable updates and need to detect whether top-level array items or object fields kept the same references
  • You want named array and object comparators without pulling a deep-equality implementation into a browser route
  • Your inputs may be null or undefined and you want identical nullish values handled by the same API
  • You need bundled TypeScript declarations and compatibility with both require() and ESM import
Skip it if

Setup reality

In our install, shallow-equal 3.1.0 finished in 0.6 seconds and left 1 package using 1 MB. npm audit found 0 known vulnerabilities. The package has 0 direct dependencies, 0 peers, an MIT license and 48 KB unpacked. It is CommonJS without an exports map, while require() and ESM import both worked. TypeScript declarations are included. Our browser import built to 0.7 KB minified and 0.4 KB gzipped.

Version 3.1.0 needs no credentials, config file or initialization. Import shallowEqualArrays or shallowEqualObjects when the input kind is already known. The generic shallowEqual first checks both values with Array.isArray, returns false for an array versus an object, and otherwise delegates. The README explicitly calls out that extra runtime type check as the cost of the generic form.

Comparison stops after one level. Two nested objects pass only when both parents point at the same nested reference. Object order does not matter, since the code compares key counts and then checks each key from the first input. Symbols and inherited fields are skipped. An own field set to undefined remains different from a missing field because the second object must pass hasOwnProperty for every key.

Every value comparison uses === instead of Object.is. That makes [NaN] unequal to [NaN], while [0] equals [-0]. Sparse array holes read as undefined, so a single hole compares equal to an explicit undefined element at the same index. Date and Map instances can compare equal despite different contents because they usually expose no enumerable own string keys. Use a type-aware or deep comparator for those values.

Patterns

Compare array entries at one level compare-arrays

import { shallowEqualArrays } from 'shallow-equal'

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

Every indexed value is checked with ===. Equal-looking objects fail when they are separate references.

Compare enumerable record fields compare-objects

import { shallowEqualObjects } from 'shallow-equal'

shallowEqualObjects(
  { page: 2, sort: 'newest' },
  { sort: 'newest', page: 2 },
) // true

Property order is irrelevant. Only enumerable own string keys returned by Object.keys participate.

Dispatch between array and object comparison dispatch-by-input-kind

import { shallowEqual } from 'shallow-equal'

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

shallowEqual runs Array.isArray on both values before delegating. Prefer the specific export when callers already know the input kind.

Compare records that share a nested reference preserve-child-reference

const filters = { state: 'open' }
const previous = { page: 1, filters }
const unchanged = { page: 1, filters }
const rebuilt = { page: 1, filters: { state: 'open' } }

shallowEqualObjects(previous, unchanged) // true
shallowEqualObjects(previous, rebuilt) // false

Immutable updates fit this contract when unchanged children retain their references and changed children receive new ones.

Compare optional values handle-null-values

shallowEqualObjects(undefined, undefined) // true
shallowEqualObjects(null, null)           // true
shallowEqualObjects(null, {})             // false
shallowEqualArrays(undefined, [])         // false

The declarations allow null and undefined. Only the same nullish value succeeds through the initial === check.

Keep missing and undefined fields distinct distinguish-missing-key

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

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

The comparator checks key counts and hasOwnProperty, so an absent key cannot masquerade as an own key holding undefined.

Test strict-equality number behavior account-for-number-edges

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

Strict === makes NaN unequal to itself and treats signed zero values as equal. Object.is uses the reverse behavior for both cases.

Normalize holes when array shape matters avoid-sparse-array-surprise

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 loop reads indexed values and never checks property presence. A hole therefore matches an explicit undefined element.

Use a deliberate React memo comparator compare-react-props

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

React.memo already uses Object.is for its default shallow prop check. Supplying this comparator changes the NaN and signed-zero cases.

Use type-specific checks for Date and Map avoid-special-objects

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

const sameDate = first.getTime() === second.getTime()

Date timestamps and Map entries are not enumerable own string fields, so Object.keys sees both examples as empty objects.

Cache a result by shallow argument equality build-one-entry-cache

let previousArgs
let previousResult

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

Cache correctness depends on inputs staying immutable. Mutating a retained object can hide a change because the nested reference remains identical.

Pin shallow behavior in a unit test test-reference-contract

const profile = { role: 'editor' }

expect(shallowEqualObjects(
  { profile, active: true },
  { profile, active: true },
)).toBe(true)

expect(shallowEqualObjects(
  { profile },
  { profile: { role: 'editor' } },
)).toBe(false)

Test both retained and recreated child references. That catches a refactor that silently changes the application's immutability assumptions.

Alternatives

PackageRegistryPick it when
fast-shallow-equalnpmChoose it when benchmarked shallow object comparison is the only operation you need
shallowequalnpmChoose it when an established single shallow comparator better matches an older dependency's API
fast-deep-equalnpmChoose it when nested object contents must compare structurally and its supported built-in types cover your data
dequalnpmChoose it for a compact deep comparator with documented support for Map, Set, Date and typed arrays

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.