Set.prototype.has()

Tests whether a value is in the collection, in time that stays sublinear as it grows.

Signature
has(value)
  • Added in JavaScript ES2015

Parameters

ParameterTypeDescription
$valueanyThe value to test for. Compared with SameValueZero, so NaN matches NaN and 0 matches -0; objects match only by reference.

Return value

booleantrue if the value is present, otherwise false.

Errors

Throws a TypeError if called on a value that is not a real Set instance. Never throws for a missing value.

has() is the reason to pick a Set at all. The specification requires implementations to provide "access times that are sublinear on the number of elements", so engines back it with a hash table — the check does not get slower as the collection fills up. Array.prototype.includes() walks the array, so it degrades linearly. On a few dozen items the difference is noise; on tens of thousands, inside a loop, it is the difference between instant and hung.

The rewrite that matters

The classic fix is turning a nested scan into a single pass:

const blocked = new Set(blockList);
const allowed = users.filter((u) => !blocked.has(u.id));

Building the Set costs one pass; each lookup then costs about the same regardless of list length. Building it inside the filter callback throws that away — hoist it out.

How values are compared

Comparison is SameValueZero. That means no type coercion (has("1") is false for a stored 1), NaN is findable where indexOf would fail, and 0 and -0 are one value. Objects match on identity only, so a freshly built literal will never be found.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Filter a large list against a block-list or allow-list without a nested loop
  • Detect a cycle by testing whether a node has already been visited
  • Validate that a submitted value is one of a known set of enum strings
  • Test for NaN membership, which Array.prototype.indexOf cannot do

Frequently asked questions

Is Set has() faster than Array includes()?

Yes — the specification requires sublinear access, so has() stays roughly constant while includes() scans the whole array. The advantage grows with collection size, and disappears if you rebuild the Set on every lookup.

Why does has() return false for an object that looks identical?

Because objects are compared by reference, so only the exact object you inserted is found. Store a primitive key (an id or a serialized string) if you need comparison by contents.

Can a Set find NaN?

Yes. Comparison uses SameValueZero, under which NaN equals itself, so has(NaN) returns true — unlike Array.prototype.indexOf, which always reports -1 for NaN.

More examples

Replacing a nested includes() scan

Both lines print [1, 3], but the second does one pass to build the lookup and then a cheap test per user instead of rescanning the block-list every time. The saving only exists if the Set is built outside the callback.

Example · javascript
const users = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }];
const blockList = [2, 4];

// Linear scan per user
console.log(users.filter((u) => !blockList.includes(u.id)).map((u) => u.id));

// Hoist the Set out of the callback, then look up in sublinear time
const blocked = new Set(blockList);
console.log(users.filter((u) => !blocked.has(u.id)).map((u) => u.id));

Objects match by identity

The stored reference is found, the identical-looking literal is not — has() never inspects properties. Mapping to a primitive key first is the standard workaround when you want value equality.

Example · javascript
const a = { key: 1 };
const registry = new Set([a]);

console.log(registry.has(a));
console.log(registry.has({ key: 1 }));

// Index by a primitive when contents are what matter
const byKey = new Set([a].map((o) => o.key));
console.log(byKey.has(1));

Detecting a cycle in a linked structure

The loop stops the moment has() reports a node it already recorded, printing the traversal order and the node where the cycle closes. Without the constant-time check this pattern would rescan the visited list on every step.

Example · javascript
const nodes = { a: "b", b: "c", c: "a" };

let current = "a";
const visited = new Set();
while (!visited.has(current)) {
  visited.add(current);
  current = nodes[current];
}

console.log([...visited]);
console.log("cycle re-enters at:", current);

Discussion

  • Be the first to comment on this lesson.