Stop turning everything into arrays (and do less work instead)
Most front-end code processes data long before it ever hits the screen. We fetch a list, tweak it, trim it down, and repeat. And usually without thinking too hard about how much work we’re doing along the way.
For years, modern JavaScript has pushed us toward a familiar pattern:
data
.map(...)
.filter(...)
.slice(...)
.map(...)
It’s readable and expressive. It’s also eager, allocates multiple arrays, and often does unnecessary work.
Iterator helpers in JavaScript gives us a native, lazy alternative that’s especially relevant for dealing with large datasets, streams, and UI-driven logic.
Arrays everywhere (and way more work than necessary)
Consider this UI scenario:
- You fetch a large dataset
- You filter it
- You take the first few results
- You render them
const visibleItems = items
.filter(isVisible)
.map(transform)
.slice(0, 10);
Looks harmless, right? Under the hood:
filtercreates a new arraymapcreates another arrayslicecreates yet another array
Even if you only need 10 items, you might’ve processed thousands. That mismatch between what you describe and what actually runs is where iterator helpers pay off.
So, what are iterator helpers?
Iterator helpers are chainable methods on iterator objects, not arrays.
That distinction matters: arrays don’t gain these methods directly. You need an iterator from values(), keys(), entries(), or a generator. Then you can build a lazy pipeline on top of it.
They let you do things like:
mapfiltertakedropflatMapfind,some,everyreducetoArray
Most of these helpers are lazy, meaning they only pull values as needed.
In general, laziness means:
- No intermediate arrays
- No unnecessary work
- Computation stops as soon as it can
You describe what you want, and the runtime pulls values only when needed.
Lazy by default
Here’s the same logic with iterator helpers:
const visibleItems = items
.values()
.filter(isVisible)
.map(transform)
.take(10)
.toArray();
What changed?
items.values()gives you an iterator, not an array- Each step runs only when the next value is requested
- Processing stops after 10 matches
What this buys you in real apps
It’s not always about raw speed, it’s about avoiding unnecessary work. Iterator helpers unlock better UI patterns.
Rendering large lists
If you’re dealing with:
- Virtualized lists
- Infinite scrolling
- Large tables
Lazy iteration means you don’t process items that never reach the screen:
function* rows(data) {
for (const row of data) {
yield renderRow(row);
}
}
const visibleRows = rows(data)
.filter(isInViewport)
.take(20)
.toArray();
You render exactly what you need and nothing more.
Streaming & async data
Async iterables have their own iterator helpers, which makes them a great fit for paginated APIs and streams:
async function* fetchPages() {
let page = 1;
while (true) {
const res = await fetch(`/api/items?page=${page++}`);
if (!res.ok) return;
yield* await res.json();
}
}
const firstTen = await fetchPages()
.filter(isValid)
.take(10)
.toArray();
No buffering entire responses, no manual counters. Just describe the pipeline and let the runtime pull what’s needed.
Cleaner data pipelines (without utility libraries)
Before iterator helpers, you might’ve reached for libraries just to get lazy pipelines. Now it’s part of the language:
const ids = users
.values()
.map(u => u.id)
.filter(Boolean)
.toArray();
Readable, native, zero dependencies.
Iterator helpers vs. array methods
| Array methods | Iterator helpers |
|---|---|
| Eager | Lazy |
| Allocate new arrays | Minimal allocations |
| Always process all items | Can stop early |
| Familiar | Slight learning curve |
When not to use iterator helpers
Iterator helpers are powerful, but they’re not a replacement for arrays everywhere. They’re a poor fit when:
- You need random access (
items[5]) - You rely heavily on array mutation
- Your data size is small and simplicity wins
Gotchas you should know about
Iterator helpers behave differently than arrays in a few important ways.
| Gotcha | What it means | Why it matters |
|---|---|---|
| One-shot iterators | Once consumed, they’re done | You can’t reuse the same pipeline twice |
| Lazy execution | Nothing runs until consumption | Side effects may appear “missing” |
| Sequential only | No random access | Patterns like items[5] don’t translate |
| Debugging consumes data | Logging can advance the iterator | console.log can change behavior |
Can I use this today?
Iterator helpers are supported in all modern browser (Chrome 122+, Firefox 131+, Safari 18.4+, Edge 122+) and Node.js 22+.
Doing less work on purpose
For a long time JavaScript trained us to eagerly turn everything into arrays. Iterator helpers give us another option:
- Do less work
- Allocate less memory
- Write pipelines that match how UIs actually behave
And once you get used to lazy iteration, going back to eager chains feels a bit wasteful.