How Lodash forIn Bypasses ES6 Symbol Properties

Lodash’s _.forIn method is designed to iterate over an object's own and inherited enumerable properties, but it completely ignores properties keyed by ES6 Symbols. This behavior is intentional, rooted in the method's strict adherence to standard ECMAScript for...in semantics and its internal key-enumeration architecture. This article details the underlying mechanisms—specifically native property traversal algorithms and the intentional exclusion of modern reflection APIs—that cause _.forIn to bypass Symbol-mapped properties.

Reliance on Standard for...in Semantics

The primary reason _.forIn ignores Symbol keys is that it models the behavior of JavaScript's native for...in loop. When the ECMAScript 2015 (ES6) specification introduced the Symbol primitive, it defined Symbols to represent non-string object keys intended for private metadata and extension-safe properties.

Under the ECMAScript specification, standard enumeration algorithms—such as [[Enumerate]] and operations driving the native for...in statement—are explicitly restricted to string-keyed properties. Because Lodash’s _.forIn is built directly on top of or as a polyfill for this native looping construct, it automatically adopts the engine's built-in exclusion of Symbol identifiers.

Internal Traversal via baseFor and Key Extraction

Under the hood, Lodash implements _.forIn using an internal iterator, typically baseFor or baseForOwn, which accepts an iteration function. In standard execution paths, _.forIn takes one of two approaches depending on optimization flags and target environments:

  1. Direct Native Loop: It evaluates the object using a standard for (var key in object) construct. In any ES6-compliant engine, this native loop automatically ignores Symbol keys, even if the property descriptor marks the Symbol as enumerable: true.
  2. Internal Key Lists via keysIn: If the operation maps keys to an array first (as seen in variations like _.forInRight), it relies on baseKeysIn. This helper crawls the object and its prototype chain collecting string properties via Object.keys() or traditional for...in filters.

Neither execution path incorporates APIs required to reveal Symbol properties.

Deliberate Omission of Symbol Reflection APIs

In JavaScript, Symbol keys cannot be retrieved through general property enumerators; they require dedicated reflection functions. An engine only exposes Symbols if code explicitly invokes:

Lodash includes utilities that retrieve Symbols when explicitly necessary (for example, internal cloning or assigning routines such as baseClone and _.merge), where it combines keys with getOwnPropertySymbols. However, _.forIn deliberately omits these calls.

Because _.forIn does not traverse prototype chains using Object.getPrototypeOf() combined with Object.getOwnPropertySymbols(), Symbol-mapped properties never enter the iteration pipeline.

Demonstrating the Behavior

The divergence between string keys and Symbol keys in _.forIn can be observed in basic usage:

const _ = require('lodash');

const symKey = Symbol('id');
const object = {
  regularKey: 'visible',
  [symKey]: 'hidden'
};

// Ensure the Symbol property is explicitly enumerable
Object.defineProperty(object, symKey, {
  enumerable: true,
  value: 'still hidden'
});

_.forIn(object, (value, key) => {
  console.log(key, value);
});
// Output:
// 'regularKey' 'visible'

Even with enumerable set to true, the Symbol key is omitted. By combining the default filtering of native for...in loops with an internal implementation that abstains from calling Object.getOwnPropertySymbols, _.forIn maintains legacy specification compatibility and prevents unintended access to Symbol-mapped metadata.