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:

  1. Use _.differenceBy: Returns a new array with the matching elements removed, leaving the original array intact.

    const result = _.differenceBy(frozenList, [{ id: 1 }], 'id');
  2. 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');