Set.prototype.forEach()

Runs a callback once per element in insertion order, with no return value.

Signature
forEach(callbackFn)
forEach(callbackFn, thisArg)
  • Added in JavaScript ES2015

Parameters

ParameterTypeDescription
$callbackFnFunctionA function executed for each element, called with three arguments: value (the element), key (identical to value, for API symmetry with Map) and set (the collection being traversed).
$thisArgoptionalany= undefinedA value to use as this when executing callbackFn. Ignored for arrow functions, which take this from their enclosing scope.

Return value

undefinedundefined.

Errors

Throws a TypeError if callbackFn is not callable, or if called on a value that is not a real Set instance.

forEach() visits each element in insertion order and returns undefined. The callback receives the value twice — as value and again as key — plus the collection itself, matching Map.prototype.forEach()'s signature. A callback written as (value, index) => out of array habit will find the value in both slots, never a number.

forEach or for...of?

Prefer for...of in most code: it supports break, continue, and await, none of which work here. There is no way to stop a forEach() early short of throwing. Its real advantages are passing an existing named function directly, and the thisArg parameter, which binds this for an old-style method without a bind() call — note that thisArg has no effect on arrow functions.

Mutating during the pass

Deleting an element the loop has not reached yet means it is never visited; adding an element mid-pass means it will be visited before the loop ends. A value deleted and then re-added is visited a second time at its new position. Elements holding undefined are visited normally — there are no holes.

Example

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

When to use it

  • Log or report every member of a collection without needing a result array
  • Apply an existing named handler function to each element
  • Bind an accumulator object as this while tallying values
  • Copy a collection's elements into another structure during a single pass

Frequently asked questions

How do I break out of a Set forEach() loop?

You cannot — return only ends the current callback invocation, and the traversal always runs to completion. Use for...of with break, or some()/every() on a spread array, when you need to stop early.

Why does the forEach callback get the same value twice?

The second parameter is the key, and a Set's key is its value, so the signature matches Map.prototype.forEach(). There is no index available, so naming it index will mislead you.

Can I use await inside a Set forEach() callback?

Not usefully — forEach ignores the returned promises, so the loop finishes before the async work does. Use for...of with await, or collect the promises and pass them to Promise.all().

More examples

No early exit

return inside the callback only ends that one invocation — the traversal still runs to the end, so this is a filter, not a break. The for...of version below actually stops after 2, which is why it is the better default.

Example · javascript
const s = new Set([1, 2, 3, 4, 5]);

const visited = [];
s.forEach((n) => {
  if (n > 2) return; // skips the body, does NOT stop the loop
  visited.push(n);
});
console.log(visited, "but every element was still visited");

for (const n of s) {
  if (n > 2) break;
  console.log("for...of saw", n);
}

Mutating the collection mid-pass

Deleting "b" before the cursor reaches it removes it from the traversal, while "d" is added ahead of the cursor and does get visited — the order is ["a", "c", "d"]. Both effects are specified behaviour, not implementation quirks.

Example · javascript
const s = new Set(["a", "b", "c"]);
const order = [];

s.forEach((v) => {
  order.push(v);
  if (v === "a") {
    s.delete("b");
    s.add("d");
  }
});

console.log(order);
console.log([...s]);

thisArg versus an arrow function

Passing collector as thisArg lets a detached method keep its receiver without bind(). The arrow callback prints false because arrows close over the surrounding this and thisArg cannot override it.

Example · javascript
const collector = {
  items: [],
  push(v) { this.items.push(v); },
};

new Set(["x", "y"]).forEach(collector.push, collector);
console.log(collector.items);

// An arrow ignores thisArg entirely
const probe = { seen: 0 };
new Set([1]).forEach(() => console.log(this === probe), probe);

Discussion

  • Be the first to comment on this lesson.