The newest JavaScript
Three additions that remove a page of helper code each: iterator helpers that work a step at a time, set methods that do real set algebra, and the small conveniences around them.
Iterator helpers
An array has .map and .filter, but they build a whole new array before the next question can be asked. An iterator helper works a step at a time: scores.values().map(double).take(2).toArray() doubles the first two scores and stops there — the rest of the input is never visited.
Generators, Map, Set and String all hand out iterators, so the same chain works on every one of them. Nothing runs until a result is asked for: .toArray(), .reduce(), .forEach(), or a for…of loop.
Array methods are eager: each one finishes before the next begins, and each builds a full array. An iterator helper is lazy, which is why a chain with a filter and a take can skip most of the input. The two styles look alike, and the difference is when the work happens.
Set methods are set algebra
A Set has held unique values for years; now it does the real operations: a.intersection(b) for what both hold, a.union(b) for everything, a.difference(b) for what only the first holds, a.symmetricDifference(b) for what exactly one holds, and a.isSubsetOf(b) for the question in its name.
Each of these used to be a filter with a has inside, or a loop with a second set to collect into. The method says the idea instead of the steps, and the browser does it without building intermediates you never look at.
These methods return a new set rather than changing either operand, so a chain of them reads like an expression. They also accept any iterable, not only another set, which is why a plain array can be passed to union without a conversion.
The small conveniences
Object.groupBy(items, fn) folds an array into an object keyed by what the function returns — the same shape as the reduce you may have written, without the accumulator lines. The result's keys are the group names, and the values are arrays of the items that landed in each.
Promise.try(fn) starts a chain with a function that may throw before it returns anything: the error arrives in the same .catch as a rejected promise would, sync or async. Array.fromAsync(source) collects values from an async source without a loop of awaits.
Lazy means nothing has happened yet
An iterator helper chain is a description of work, not the work itself. scores.values().map(double) returns an iterator; nothing is doubled until something asks for a value. .toArray(), .reduce() and for…of are the asking — the terminal operations.
That is what lets .take(2) save the rest of the input: only two values are ever pulled through the chain. An iterator is also consumed once. After it has been walked to the end, asking again gives nothing, so build a fresh chain rather than storing a half-used one.
Sets compare by identity, groupBy by key
A Set uses SameValueZero: primitives are compared by value, so two "Ada" strings are one member, while two separately created objects are always two members even when their fields match. That is the same identity rule === follows, and it is why sets of objects are more limited than they first appear.
Object.groupBy uses the returned value as a property key, and property keys are strings — so a number 40 becomes "40". Map.groupBy keeps the key as it was, numbers included, and is the right choice when the key is not naturally text.
A worked chain, value by value
Take scores holding 42, 17, 88, 5 and 63, and a chain that keeps the values over 20, doubles each survivor, and takes the first two. Nothing happens when the chain is built. .toArray() asks for the first value: the filter pulls 42, keeps it, the map doubles it to 84, and the take passes it on. The second request pulls 17, which the filter drops, then 88, which becomes 176 and fills the take.
The third request is never made, so 5 and 63 are never read from the array. The work stops at the last value the result needs, and an array method chain would have processed all five before the take could discard the rest.
The same work, said shorter
const scores = [42, 17, 88, 5, 63];
const doubled = scores.values().map((n) => n * 2).toArray();
const teamA = new Set(['Ada', 'Grace', 'Alan']);
const teamB = new Set(['Grace', 'Linus', 'Ada']);
const shared = teamA.intersection(teamB);
const grouped = Object.groupBy(scores, (n) => (n >= 40 ? 'pass' : 'fail'));
console.log(doubled, shared, grouped);Three mistakes: building an iterator chain and never consuming it, so the console stays empty; walking the same iterator twice and wondering why the second answer is empty; and expecting Object.groupBy keys to stay numbers — they become strings, while Map.groupBy preserves the original key.
Try these next
- Add
.take(2)to an iterator chain and confirm the rest of the array is never visited. - Group the same numbers with
Map.groupByand compare the key types withObject.groupBy. - Store an iterator, consume it with
toArray(), then try to consume it again.
Say it in one line
Double every score with an iterator helper, find what the two teams share with a set method, and group the scores into pass and fail with Object.groupBy. Log all three.
3 left to do
- The scores go through an iterator helper
- The teams are compared with a set method
- The scores are grouped by pass and fail
Scores: scores.values().map((n) => n * 2).toArray(). Teams: make Sets and call .intersection(...). Groups: Object.groupBy(scores, (n) => (n >= 40 ? 'pass' : 'fail')).
Take only what you need
Build an iterator chain that keeps the scores over 20, doubles each one, and takes the first three. Then group the original scores by whether they pass 40, keeping the keys as numbers.
3 left to do
- The chain takes only three values
- The chain is consumed with toArray
- The groups keep numeric keys
scores.values().filter((n) => n > 20).map((n) => n * 2).take(3).toArray() — then Map.groupBy(scores, (n) => (n >= 40 ? 1 : 0)).
These arrived in browsers through 2024 — Chrome 122 and Safari 17.4 onwards for the iterator and set methods. The preview runs in your own browser, so an older one will say so in the console: that is the only real cost of using the language itself.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does an iterator helper avoid that an array method does not?
