jsguides

Set.prototype.has()

set.has(value)

The has() method returns a boolean indicating whether a Set contains a specific value.

Syntax

set.has(value)

Parameters

ParameterTypeDescription
valueanyThe value to test for existence in the Set

Return Value

Returns true if the value exists in the Set; otherwise returns false.

Description

The has() method is the primary way to check for element existence in a Set. Unlike arrays where you might use Array.prototype.includes() or ArrayindexOf(), Set membership checks are constant time — O(1) — regardless of how many elements the Set contains.

This makes Sets dramatically faster for membership testing, especially with large collections. While an array search scales linearly (O(n)) with each additional element, a Set lookup takes the same time whether it contains 10 or 10 million items.

Equality Semantics

has() uses strict equality (===) to compare values:

  • Primitives (numbers, strings, booleans, null, undefined, Symbol, BigInt): Compared by value. 1 and '1' are different.
  • Objects: Compared by reference, not by value. Two objects with identical properties are not equal unless they reference the exact same object in memory.
  • Special case: NaN is treated as equal to another NaN, which differs from === behavior where NaN === NaN returns false.

Examples

Basic existence check

const colors = new Set(['red', 'green', 'blue']);

console.log(colors.has('red'));
// true

console.log(colors.has('yellow'));
// false

console.log(colors.has('RGB'));
// false (case-sensitive)

Simple value checks like the ones above work intuitively because primitives are compared by value. Object checks require more care: two objects with identical properties are distinct references. Understanding this distinction is essential when Sets hold objects instead of strings or numbers.

Checking with objects and references

const user1 = { id: 1, name: 'Alice' };
const user2 = { id: 2, name: 'Bob' };
const users = new Set([user1]);

console.log(users.has(user1));
// true (same reference)

console.log(users.has({ id: 1, name: 'Alice' }));
// false (different object, different reference)

const id = { id: 1 };
users.add(id);
console.log(users.has(id));
// true (same reference)

Once you understand how reference equality drives Set membership, the performance advantage of has() over array methods becomes practical to measure. The benchmark below compares a linear array search against a constant-time Set lookup using a collection of 10,000 elements.

Performance comparison with Array.includes()

// Create a large dataset
const values = Array.from({ length: 10000 }, (_, i) => i);
const set = new Set(values);
const array = [...values];

const target = 9999;

// Array.includes() - O(n) linear search
console.time('Array.includes');
array.includes(target);
console.timeEnd('Array.includes');
// ~0.3ms (scales with array size)

// Set.has() - O(1) constant time
console.time('Set.has');
set.has(target);
console.timeEnd('Set.has');
// ~0.01ms (constant, regardless of Set size)

// With larger datasets, the difference becomes dramatic
// Array: 10ms → 100ms → 1000ms as size grows
// Set: 0.01ms → 0.01ms → 0.01ms (stable)

The performance characteristics make has() a natural fit for patterns that involve repeated membership testing. The examples below show three practical scenarios — tracking visited items, filtering duplicates, and gating operations — each of which benefits from the constant-time guarantee.

Common Patterns

Tracking visited items

const visited = new Set();

function visit(url) {
  if (visited.has(url)) {
    console.log('Already visited:', url);
    return false;
  }
  visited.add(url);
  return true;
}

visit('https://example.com');
// true
visit('https://example.com');
// Already visited: https://example.com
// false

Tracking visited state with a Set is naturally paired with duplicate prevention. Once you have the mechanism to record what has been seen, extending it to filter an entire collection before processing becomes straightforward. The next pattern shows how has() can drive deduplication in a single pass over an array of objects.

Preventing duplicates in processing

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 1, name: 'Alice' } // duplicate
];

const seenIds = new Set();
const unique = users.filter(user => {
  if (seenIds.has(user.id)) return false;
  seenIds.add(user.id);
  return true;
});

console.log(unique.length);
// 2

Deduplication answers “have we seen this?” for each item. The next logical step is testing membership before performing an action — asking “should we allow this?” A Set of allowed operations pairs naturally with has() to gate access without any iteration or index-based searching. The intent is explicit and the performance stays constant.

Set membership before operations

const allowedActions = new Set(['read', 'write', 'delete']);

function performAction(user, action) {
  if (!allowedActions.has(action)) {
    console.log(`Invalid action: ${action}`);
    return false;
  }
  
  console.log(`${user} performed ${action}`);
  return true;
}

performAction('Alice', 'read');
// Alice performed read
performAction('Bob', 'execute');
// Invalid action: execute

Each of the patterns above — visited tracking, deduplication, and membership gating — relies on the same has() call behaving quickly regardless of Set size. The performance characteristic that makes this possible is worth understanding on its own.

Why set.has() scales well

Set.prototype.has() is designed for membership checks, so it stays fast even as the collection grows. That is different from arrays, where each lookup may need to scan several values before it finds a match. When you repeatedly test for the same kind of item, a Set can turn that repeated search into a simple existence check.

This is why Set is such a common choice for deduplication, visited-state tracking, and allowlists. The code is often cleaner too, because the intent is not “find this index” but “does this value belong here?” A Set expresses that question directly.

When arrays are still fine

An array is still a good fit when order matters more than membership speed or when the collection is tiny. In those cases, the overhead of creating a Set may not buy you much. If you only need one or two checks, an array can be perfectly readable and easier to keep in a simple data flow.

The tradeoff becomes important when you perform many checks against the same list. If the code keeps asking “have we seen this before?” or “is this action allowed?” then Set.has() usually becomes the better model. The rule of thumb is simple: use the structure that matches the question you are asking most often.

See Also