Set.prototype.has()
Tests whether a value is in the collection, in time that stays sublinear as it grows.
has(value)- Added in JavaScript ES2015
Parameters
| Parameter | Type | Description |
|---|---|---|
$value | any | The 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
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.
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.
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.
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