Skip to content

assert.deepEqual([null], [undefined]) behavior changed in v22.16.0 (undocumented breaking change) #61583

@iholovin

Description

@iholovin

Version

v22.16.0+ (and v24.x)

Platform

All platforms

Subsystem

assert

What steps will reproduce the bug?

const assert = require('assert');
assert.deepEqual([null], [undefined]);

How often does it reproduce? Is there a required condition?

Always reproduces on Node >= 22.16.0. Passes on Node <= 22.15.0.

What is the expected behavior? Why is that the expected behavior?

The assertion should pass because:

  1. The documentation states: "Primitive values are compared with the == operator"
  2. In JavaScript, null == undefined is true
  3. This worked in all Node versions prior to 22.16.0

What do you see instead?

AssertionError [ERR_ASSERTION]: Expected values to be loosely deep-equal:

[ null ]

should loosely deep-equal

[ undefined ]

Root Cause

PR #57619 (labeled as "performance improvement") introduced this change. The modification in objEquiv now checks a[i] !== undefined with strict equality (===) before calling innerDeepEqual, which would have used loose equality (==):

// Before: called innerDeepEqual() which respects loose equality mode
if (!innerDeepEqual(a[i], b[i], mode, memos)) { return false; }

// After: checks with strict equality BEFORE calling innerDeepEqual
if (b[i] === undefined) {
  if (a[i] !== undefined || !hasOwn(a, i))  // <-- uses === not ==
    return false;
}

When comparing [null] vs [undefined]:

  • b[0] is undefined → enters first branch
  • a[0] !== undefinednull !== undefined is true (strict equality)
  • Returns false immediately, never calls innerDeepEqual which would use ==

Additional information

  • This is an undocumented breaking change in an LTS release
  • The PR was not labeled as semver-major
  • Affects anyone comparing arrays that may contain null values against expected undefined values (common with MongoDB/BSON which stores undefined as null)

I understand deepEqual is marked as "Legacy" and docs warn it "can have surprising results", but this specific change contradicts the documented behavior and broke existing code without notice.

Metadata

Metadata

Assignees

No one assigned

    Labels

    assertIssues and PRs related to the assert subsystem.confirmed-bugIssues with confirmed bugs.

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions