Lodash _.pullAllBy with Read-Only Properties
This article explores the behavior and consequences of executing
Lodash's _.pullAllBy method against an array containing
read-only elements or properties. Because _.pullAllBy is a
mutating method designed to modify an array in place, the outcome
depends strictly on whether the "read-only" restriction applies to the
outer array's structure or to the properties of the objects stored
within it. Below, we break down the JavaScript runtime errors, silent
failures, and safe alternative patterns for both scenarios.
Understanding How _.pullAllBy Works
The _.pullAllBy method accepts an original array, an
array of values to remove, and an iteratee function to determine
equality.
Unlike filtering methods that return a new array,
_.pullAllBy mutates the target array directly. Under the
hood, Lodash iterates through the target array, compares values using
the iteratee, shifts remaining items forward to fill empty spaces, and
truncates the array length.
Scenario 1: Objects with Read-Only Properties
If you pass an array of objects where the individual object
properties are marked as read-only (such as via
Object.defineProperty with writable: false or
Object.freeze), the operation succeeds without
error.
const item1 = Object.freeze({ id: 1, name: 'Alice' });
const item2 = Object.freeze({ id: 2, name: 'Bob' });
const list = [item1, item2];
_.pullAllBy(list, [{ id: 1 }], 'id');
console.log(list); // Output: [{ id: 2, name: 'Bob' }]Why It Works
_.pullAllBy reads object properties via the iteratee for
comparison purposes, but it never modifies the objects themselves. It
only updates the references stored at the array's index positions and
alters the array's length. Because the inner objects are
only read and not mutated, their read-only constraints are never
violated.
Scenario 2: Arrays with Read-Only Indices or Frozen Arrays
If the array itself has read-only index properties—most commonly
created using Object.freeze() or custom property
descriptors on the array indices—the execution fails and throws
a runtime exception.
const frozenList = Object.freeze([{ id: 1 }, { id: 2 }]);
// Throws TypeError: Cannot assign to read-only property
_.pullAllBy(frozenList, [{ id: 1 }], 'id');The Resulting Error
When JavaScript runs in strict mode (the default in ES modules and modern tooling), modifying a frozen or non-writable array property throws:
TypeError: Cannot assign to read-only property '0' of object '[object Array]'
or
TypeError: Cannot assign to read-only property 'length' of object '[object Array]'
In non-strict mode, the mutation attempts will fail silently: Lodash
will attempt to write elements to indices and truncate
length, but because the properties cannot be overwritten,
the array remains unchanged or ends up in an inconsistent state.
Safe Alternatives
To avoid runtime exceptions when handling arrays that cannot or should not be mutated, use non-mutating equivalents:
Use
_.differenceBy: Returns a new array with the matching elements removed, leaving the original array intact.const result = _.differenceBy(frozenList, [{ id: 1 }], 'id');Clone Before Pulling: Create a shallow copy of the array if you specifically need an array instance that can be pulled from.
const mutableCopy = [...frozenList]; _.pullAllBy(mutableCopy, [{ id: 1 }], 'id');