How Lodash isSet Distinguishes Set from WeakSet

This article explains how the Lodash utility function _.isSet identifies standard Set instances and reliably excludes WeakSet collections. It details the underlying type-checking mechanisms Lodash applies, including internal string tags, Node.js native type bindings, and the structural differences between these two built-in JavaScript objects.

The Core Implementation of _.isSet

Lodash determines whether a given value is a standard Set through an environment-aware approach. In modern environments (such as Node.js), it prioritizes native type identifiers. In standard browser environments or runtimes without native helpers, it falls back to internal tag checks via Object.prototype.toString.

In both approaches, WeakSet instances are explicitly excluded because JavaScript treats Set and WeakSet as fundamentally distinct types with different internal slots.

Fallback Mechanism: baseGetTag and [object Set]

In the standard fallback implementation (baseIsSet), Lodash performs two primary checks:

  1. Object-Like Validation (isObjectLike):
    The target must not be null, and its typeof result must be 'object'. Both Set and WeakSet pass this initial check.

  2. Internal Tag Extraction (getTag):
    Lodash calls an internal utility, baseGetTag, which relies on Object.prototype.toString.call(value).

    JavaScript defines different Symbol.toStringTag values for Set and WeakSet:

    • Object.prototype.toString.call(new Set()) evaluates to "[object Set]"
    • Object.prototype.toString.call(new WeakSet()) evaluates to "[object WeakSet]"

Lodash compares the extracted string directly against the constant setTag ("[object Set]"):

const setTag = '[object Set]';

function baseIsSet(value) {
  return isObjectLike(value) && getTag(value) == setTag;
}

Because new WeakSet() yields "[object WeakSet]", the condition evaluates to false.

Native Node.js Type Checks

In Node.js environments, Lodash attempts to load the internal util.types module (specifically util.types.isSet via nodeUtil.isSet).

Node's native util.types.isSet inspects the V8 engine's internal slots. A Set instance possesses the internal slot [[SetData]], whereas a WeakSet possesses [[WeakSetData]].

When nodeUtil.isSet is available, Lodash wraps it directly:

const isSet = nodeIsSet ? (value) => nodeIsSet(value) : baseIsSet;

Why WeakSets Cannot Mimic Sets

WeakSet is designed solely for holding weakly referenced objects and lacks the iterable interface, the .size property, and collection traversal methods (.forEach(), .values(), .keys()).

Because Lodash relies strictly on non-forgeable internal mechanics (either V8 internal slots via Node bindings or default Symbol.toStringTag evaluation through getTag), a WeakSet cannot trigger a false positive in _.isSet. Lodash provides a separate method, _.isWeakSet, to target WeakSet instances specifically by matching against "[object WeakSet]" or util.types.isWeakSet.