
Mastering JavaScript Iterators and Generators

Iteration is everywhere in JavaScript, but we often experience it through convenience methods rather than through the language mechanisms underneath. Arrays, for...of, spread syntax, and many libraries all rely on the same deeper protocols.
That is why iterators and generators are worth understanding directly. After that, a lot of JavaScript starts to feel more coherent, especially when we want lazy evaluation, custom iterable objects, or clearer control over sequence generation.
What an Iterator Actually is
An iterator is an object with a next() method. Each call returns an object with a value and a done flag. That is the basic protocol.
An iterable is slightly different. It is an object that provides an iterator via Symbol.iterator, which is why arrays, strings, maps, and sets work with for...of.
That distinction is more useful than it first sounds. for...of does not care whether the value is an array. It only cares whether the value is iterable. That is why custom iterable objects can slot so neatly into everyday JavaScript syntax.
Why Generators Matter
Writing a custom iterator by hand is possible, but usually clumsy because we have to manage internal state manually. Generator functions make that much easier:
const makeRange = function* ( start: number, end: number, step = 1): Generator<number> { for (let index = start; index < end; index += step) { yield index; }};for (const value of makeRange(0, 5)) { console.log(value);}This is elegant because the function reads like the sequence it produces. We do not have to simulate a state machine manually, because the language handles that pause‑and‑resume behaviour for us.
Lazy Evaluation is the Real Superpower
Generators produce values on demand. That means we do not have to allocate an entire array up front, which is useful for large or even potentially unbounded sequences.
This matters in two ways. Sometimes it does improve memory behaviour. Just as often, though, it improves the shape of the API. Instead of "build the whole list and then hand it over", we can express "produce values as the consumer asks for them". That is a better fit for streams, tree traversal, paginated data, and long‑running sequence generation.
The Iterable Protocol is Already Everywhere
One reason iterators feel obscure is that most of the time we use them indirectly:
for...ofconsumes an iterator- spread syntax consumes an iterator
Array.from()consumes an iteratorMapandSetexpose iterables of their contents
Once that clicks, iterators stop feeling like a niche feature. They are not living off in a corner of the language. They are underneath some of the most ordinary code we write.
Building a Custom Iterable
We can also make our own iterable objects:
type TaskList = { tasks: string[]; [Symbol.iterator](): Generator<string>;};const backlog: TaskList = { tasks: ['Design', 'Build', 'Test'], *[Symbol.iterator]() { for (const task of this.tasks) { yield task; } },};This lets backlog work naturally with for...of, spread syntax, and any API that expects an iterable.
That is a very useful design trick when we want an object to feel like a collection without pretending it is literally an array. We keep the richer object shape, but still make iteration pleasant for the consumer.
Generator Delegation and Composition
Another feature worth knowing is yield*, which lets one generator delegate to another:
const makeLabels = function* (): Generator<string> { yield 'Backlog'; yield* backlog; yield 'Done';};This is often a cleaner way to compose sequences than pushing values through several temporary arrays. It keeps the lazy behaviour and makes the intent more obvious.
When Iterators and Generators are Useful
- modelling sequential data lazily
- building reusable traversal APIs
- simplifying stateful iteration logic
- expressing complex sequence generation more readably
They are not necessary for every loop, of course. Sometimes a plain array method is clearer. The point is not to reach for generators everywhere, but to recognise when they are the right level of abstraction.
Easy Things to Get Wrong
People often assume that generators are mainly about performance. Sometimes they help performance, but their primary value is often architectural clarity around lazy sequences and stateful iteration.
Another easy detail to miss is that iterators are consumable. Once a generator has been exhausted, it does not start over automatically. If we need repeated traversal, we usually need a fresh generator or an iterable object that can create one each time.
It can also be tempting to overuse generators for simple collection work. If all we are doing is mapping five array items once, a generator is probably more abstraction than the problem needs.
If we want to read the deeper language details behind these examples, the references below are worth a look:
Wrapping up
Iterators and generators matter because they reveal how JavaScript models sequences underneath many everyday features. When we understand those protocols, we can build APIs that are lazier, clearer, and more expressive, without losing readability. That makes them far more practical than they first appear.
Key Takeaways
- Iterators expose
next(), whilst iterables exposeSymbol.iterator. - Generators make custom iteration dramatically easier to write and read.
- Lazy evaluation is one of the most useful reasons to reach for them.
- Many everyday language features already depend on the same protocols.
Seen as tools for expressing sequences cleanly, iterators and generators become a natural part of modern JavaScript rather than an obscure language corner.
Related Articles

Access Search Parameters in Next.js SSR'd Layout. 
Generators in JavaScript: A Beginner's Guide. Generators in JavaScript: A Beginner's Guide

Single or Double Colons in CSS Pseudo‑Elements (:before vs. ::before). Single or Double Colons in CSS Pseudo‑Elements (
:beforevs.::before)
Understanding prototype.apply() in JavaScript. Understanding
prototype.apply()in JavaScript
CSS Focus Styles for Keyboard Users Only. CSS Focus Styles for Keyboard Users Only

Static Generation vs. Server‑Side Rendering in Next.js. Static Generation vs. Server‑Side Rendering in Next.js

Delete All Local Git Branches Except for master or main. Delete All Local Git Branches Except for
masterormain
Use JavaScript to Find the Week Day from a Date. Use JavaScript to Find the Week Day from a Date

Manipulating Strings in JavaScript with split(). Manipulating Strings in JavaScript with
split()How to Check an Element Exists with and Without jQuery. How to Check an Element Exists with and Without jQuery

How to Prevent Race Conditions in JavaScript with AbortController. How to Prevent Race Conditions in JavaScript with
AbortController
Exporting and Importing Using ES6 Modules. Exporting and Importing Using ES6 Modules