How Lodash _.set Chooses Between Arrays and Objects

Lodash's _.set utility dynamically builds intermediate structures when traversing a path that does not yet exist. Rather than inspecting the current key, the method definitively infers whether to instantiate a nested Array or a nested plain Object by looking ahead to the next key in the path sequence. If that subsequent key qualifies as a valid array index, Lodash creates an empty Array; otherwise, it defaults to creating a plain Object.

The Lookahead Mechanism

When _.set(object, path, value) executes, it parses the path into an array of keys (for example, 'users[0].name' becomes ['users', '0', 'name']) and iterates through them via its internal baseSet function.

As it traverses, if the property corresponding to the current key is undefined or null, Lodash must dynamically instantiate a container. To determine the container type:

  1. It checks the next key immediately following the current segment in the path array.
  2. It evaluates that next key using an internal utility function named isIndex.
  3. If isIndex(nextKey) returns true, the current missing property is assigned an empty Array ([]).
  4. If isIndex(nextKey) returns false, the property is assigned an empty Object ({}).

How Lodash Defines an Index

The internal isIndex helper strictly validates whether a given key represents a non-negative integer index. A key is considered an index if:

Because isIndex accepts numeric strings, string representations of non-negative integers (such as '0' or '12') trigger array creation identically to integer literals.

Practical Examples

Lookahead to an Index

const obj = {};
_.set(obj, 'data.items[0]', 'value');

Result: { data: { items: ['value'] } }

Lookahead to a Non-Index

const obj = {};
_.set(obj, 'items[0].name', 'Alice');

Result: { items: [{ name: 'Alice' }] }

Edge Cases: Negative Numbers and Non-Integers

If a key contains negative numbers or floating-point representations, isIndex returns false:

const obj = {};
_.set(obj, 'values[-1].id', 123);

Because '-1' is not a non-negative integer, the lookahead fails the index test, causing obj.values to be initialized as an Object ({ '-1': { id: 123 } }) rather than an Array.