jsguides

Set.prototype.isSubsetOf()

isSubsetOf(other)

Set.prototype.isSubsetOf() returns true when every element of a Set is also present in another Set or set-like object, and false otherwise. It is the boolean counterpart to Set.prototype.intersection(): where intersection() answers “what do they share?”, isSubsetOf() answers “is the receiver entirely contained?”. It ships with the ES2025 batch of new Set methods (union, intersection, difference, symmetricDifference, isSupersetOf, isDisjointFrom).

Syntax

Set.prototype.isSubsetOf() is called on a Set instance and returns a primitive boolean describing whether the receiver is a subset of the argument:

setInstance.isSubsetOf(other)

Parameters

  • other — A Set instance, or any set-like object exposing a has method and a size property. Arrays do not qualify on their own; wrap them in new Set(arr) first.

Return value

A primitive boolean. true if every element of the receiver is also in other; false otherwise. Equal sets return true in both directions, because isSubsetOf is the inclusive subset relation, not the proper subset relation.

Description

In math notation, the result is A ⊆ B = { x | x ∈ A → x ∈ B }. The JavaScript implementation matches that definition, with one wrinkle: the receiver must be a real Set instance (the spec reads its internal [[SetData]] slot directly), while the argument only needs a has method and a size property. Because the method never iterates the argument, no keys() is required, which separates it from Set.prototype.intersection().

Membership uses SameValueZero, the same equality Set uses internally, so NaN equals NaN and +0 equals -0. The method ships in Baseline 2024 and is part of the new Set methods proposal by Michał Wadas.

A short, useful algorithm to keep in mind:

  1. If the receiver has more elements than other, return false immediately. This is a cheap fast-path that skips every membership probe.
  2. Otherwise walk the receiver in insertion order and return false on the first element that other.has(x) rejects.
  3. If every probe passes, return true.
// Algorithm in action: the first failing probe short-circuits the rest
const lhs = new Set([1, 2, 3, 4]);
const rhs = new Set([2, 4, 6, 8]);
console.log(lhs.isSubsetOf(rhs));
// false   (probe of `1` fails, the rest of the receiver is never tested)

Two practical consequences follow:

  • Equal sets are subsets of each other. If you specifically need proper subsetting (strict containment), combine the call with a size check.
  • An empty receiver is a subset of anything. Vacuous truth. The empty set qualifies against every set, including itself. Handy as a default branch in permission and feature checks.

isSubsetOf does not mutate either input and allocates no result Set, because the answer is a primitive boolean. That makes it cheap to call inside hot paths, render functions, and reducer pipelines where allocating a new collection would be wasteful.

// Cheap membership gate in a hot path
function isSubsetCached(workingSet, universe) {
  return workingSet.isSubsetOf(universe);
}

const allowed = new Set(["read", "write", "delete"]);
const requested = new Set(["read", "write"]);
console.log(isSubsetCached(requested, allowed));
// true

Examples

Test the subset relation

The common case is two real Set instances. The result is a primitive boolean, not a Set. The examples below start with the canonical math-subset question, then move into the equal-sets edge case, the set-like argument contract, and the brand check that catches a duck-typed receiver:

const fours = new Set([4, 8, 12, 16]);
const evens = new Set([2, 4, 6, 8, 10, 12, 14, 16, 18]);

console.log(fours.isSubsetOf(evens));
// true
console.log(evens.isSubsetOf(fours));
// false

Multiples of 4 in fours (4, 8, 12, 16) all appear in evens, so the first call returns true. Going the other way returns false because evens also contains 2, 6, 10, 14, 18, which never land in fours.

Equal sets are subsets of each other

isSubsetOf reports the inclusive subset relation, so two equal sets return true in both directions. If you need the strict variant, gate on size:

const set1 = new Set([1, 2, 3]);
const set2 = new Set([1, 2, 3]);

console.log(set1.isSubsetOf(set2)); // true
console.log(set2.isSubsetOf(set1)); // true

const isProperSubset = (a, b) => a.isSubsetOf(b) && a.size < b.size;
console.log(isProperSubset(set1, set2));
// false

Pass a set-like argument

The argument does not have to be a real Set. Any object that exposes has and size is accepted, which is useful when integrating with a library that already maintains a custom membership collection:

const whitelist = {
  size: 4,
  has(value) {
    return value === "a" || value === "b" || value === "c" || value === "d";
  },
};

console.log(new Set(["a", "b"]).isSubsetOf(whitelist));
// true

Note that keys() is not required here, because isSubsetOf only ever iterates the receiver. That makes it slightly more permissive on the argument side than Set.prototype.intersection(), which needs keys() when the set-like ends up on the smaller side.

NaN is treated as equal to itself

SameValueZero equality means NaN matches NaN. The result is a Set that contains NaN on both sides, so the membership probe succeeds:

console.log(new Set([NaN, 1]).isSubsetOf(new Set([NaN, 1, 2])));
// true

This differs from === but matches the behavior of every other Set operation, so values that round-trip through a Set are safe to use with isSubsetOf without any extra Number.isNaN dance.

The receiver must be a real Set

Set.prototype.isSubsetOf reads the receiver’s internal [[SetData]] slot directly, so plain objects that only look like a set throw a brand-check error. This is the same guard the spec applies to every built-in Set.prototype method, so a duck-typed shim is not interchangeable with a real Set:

const fakeSet = {
  size: 2,
  has: () => true,
  keys: function* () { yield 1; yield 2; },
};

try {
  fakeSet.isSubsetOf(new Set([1, 2, 3]));
} catch (err) {
  console.log(err.constructor.name);
  // TypeError
}

If you need to ask the subset question the other way around, flip the operands. A real Set becomes the receiver, the brand check passes, and the set-like only has to satisfy the has / size contract on the argument side:

const real = new Set([1, 2]);
const fake = { size: 5, has: () => true };

console.log(real.isSubsetOf(fake));
// true

The empty set is a subset of every set

Vacuous truth: the empty set satisfies the subset relation against any Set, including another empty Set. Use it as a default branch in feature-flag and permission checks:

console.log(new Set().isSubsetOf(new Set([1, 2, 3])));
// true
console.log(new Set().isSubsetOf(new Set()));
// true

Set.prototype.isSubsetOf() performance

Cost is O(min(n, m)) when both inputs are real Set instances, where n is the receiver’s size and m is the argument’s size. The size fast-path returns false immediately when n > m, so handing the method a small argument and a large receiver is the case it is tuned for. Each membership probe against a real Set is amortized O(1), so total work is bounded by the size of the smaller side.

// Fast-path: 5 > 2 short-circuits without probing
const big = new Set([1, 2, 3, 4, 5]);
const small = new Set([1, 2]);
console.log(big.isSubsetOf(small));
// false

Allocations: nothing for the result (a primitive boolean), no intermediate array, no result Set. That makes it cheaper than intersection() for the same logical question: a [...a].every(b.has) style fallback allocates an intermediate array and pays an extra O(n) copy. If you find yourself reaching for Array.prototype.every and a has callback, swap to isSubsetOf and skip the array materialization.

Guards benefit from the same fast-path. Wrapping an expensive membership walk in if (largeSet.isSubsetOf(universe)) returns false before probing any element when the receiver is larger than the universe, which beats a hand-rolled for loop in most realistic shapes.

Specifications

Set.prototype.isSubsetOf() is defined in ECMA-262 § 24.2.4.4, part of the 16th edition (ECMAScript 2025). The spec text is short and worth reading: it states that the receiver is a real Set instance, that the argument is read through a Set Record (so it only needs has and size), and that membership uses SameValueZero equality. The full proposal text and meeting notes live in the tc39/proposal-new-set-methods repository.

Browser support

Set.prototype.isSubsetOf() is part of the new Set methods proposal by Michał Wadas, which graduated to Stage 4 in 2023 and ships with the ECMAScript 2025 specification. Support reached Baseline 2024 in June 2024:

EngineFirst version
Chrome / Edge122 (Feb 2024)
Firefox127 (Jun 2024)
Safari17.0 (Sep 2023)
Node.js22.0.0

For older environments, both core-js and es-shims provide a polyfill. core-js ships a broad collection-level patch, while es-shims offers a smaller, focused polyfill that covers the new Set methods individually and follows the spec closely.

Polyfill pattern

If you cannot pull in a package, the spec algorithm fits in a handful of lines. This is the same shape the polyfills above use internally, written in plain JavaScript so it works on any runtime that has Set:

function isSubsetOf(a, b) {
  for (const v of a) if (!b.has(v)) return false;
  return true;
}

console.log(isSubsetOf(new Set([1, 2]), new Set([1, 2, 3])));
// true

The receiver walks; the argument is queried through has. That asymmetry is the same one the built-in method uses, which is why no keys() is required on the argument side.

See also