**Why iterators exist**
Arrays are eager — they hold all values in memory at once. Iterators are lazy — they produce one value at a time, on demand. This matters when dealing with large datasets, infinite sequences, or expensive computations where you only need some values:
```js
// Array: all 1 000 000 numbers in memory at once
const arr = Array.from({ length: 1_000_000 }, (_, i) => i);
// Generator: produces one number per .next() call, uses almost no memory
function* range(n) {
for (let i = 0; i < n; i++) yield i;
}
for (const x of range(1_000_000)) { /* ... */ }
```
**The iterator protocol**
An object is *iterable* if it has a `[Symbol.iterator]()` method that returns an *iterator*. An iterator is an object with a `next()` method returning `{ value, done }`:
```js
// Manual custom iterator — verbose but shows the mechanism:
const counter = {
[Symbol.iterator]() {
let n = 1;
return {
next() {
return n <= 3
? { value: n++, done: false }
: { value: undefined, done: true };
}
};
}
};
for (const x of counter) console.log(x); // 1 2 3
console.log([...counter]); // [1, 2, 3]
```
Built-in iterables: `Array`, `String`, `Map`, `Set`, `arguments`, `NodeList`.
**Generator functions — the easier way**
Writing iterators manually is repetitive. Generator functions (`function*`) let you write the same logic with much less code. Each `yield` pauses execution and sends a value out; calling `.next()` (or `for...of`) resumes from where it paused:
```js
function* counter(start, end) {
for (let i = start; i <= end; i++) {
yield i; // pause here, send i out
}
// implicit return: { value: undefined, done: true }
}
for (const n of counter(1, 3)) console.log(n); // 1 2 3
console.log([...counter(1, 5)]); // [1, 2, 3, 4, 5]
// Manual .next() calls:
const g = counter(1, 2);
g.next(); // { value: 1, done: false }
g.next(); // { value: 2, done: false }
g.next(); // { value: undefined, done: true }
```
A generator function returns a *generator object* — it is both iterable and an iterator, so it works with `for...of`, spread, destructuring, and direct `.next()` calls.
yield*, sending values with .next(), async generators
Create a `Counter` object that implements the iterator protocol manually. It should count from `start` to `end` (inclusive). Add a `[Symbol.iterator]()` method that returns an object with a `next()` method. Use it in a `for...of` loop.
function makeCounter(start, end) {
return {
[Symbol.iterator]() {
// return iterator object with next()
}
};
}
for (const n of makeCounter(1, 5)) {
console.log(n); // 1 2 3 4 5
}
Solution
function makeCounter(start, end) {
return {
[Symbol.iterator]() {
let current = start;
return {
next() {
if (current <= end) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
};
}
for (const n of makeCounter(1, 5)) {
console.log(n); // 1 2 3 4 5
}
Write a generator function `range(start, end, step = 1)` that yields numbers from `start` up to (but not including) `end`, incrementing by `step`. Use it to print numbers 0, 2, 4, 6, 8.
function* range(start, end, step = 1) {
// yield numbers here
}
for (const n of range(0, 10, 2)) {
console.log(n); // 0 2 4 6 8
}
Solution
function* range(start, end, step = 1) {
for (let i = start; i < end; i += step) {
yield i;
}
}
for (const n of range(0, 10, 2)) {
console.log(n); // 0 2 4 6 8
}
Write an infinite generator `fibonacci()` that yields the Fibonacci sequence indefinitely (0, 1, 1, 2, 3, 5, 8, ...). Use it to print the first 8 Fibonacci numbers by calling `.next()` or breaking out of a `for...of` loop with a counter.
function* fibonacci() {
// infinite sequence
}
const gen = fibonacci();
for (let i = 0; i < 8; i++) {
console.log(gen.next().value);
}
// 0 1 1 2 3 5 8 13
Solution
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const gen = fibonacci();
for (let i = 0; i < 8; i++) {
console.log(gen.next().value);
}
// 0 1 1 2 3 5 8 13
Write two generators: `odds(n)` yields odd numbers 1, 3, 5, ..., up to n; `evens(n)` yields even numbers 2, 4, 6, ..., up to n. Write a third generator `oddsAndEvens(n)` that uses `yield*` to first delegate to `odds(n)` then to `evens(n)`.
function* odds(n) {
for (let i = 1; i <= n; i += 2) yield i;
}
function* evens(n) {
for (let i = 2; i <= n; i += 2) yield i;
}
function* oddsAndEvens(n) {
// use yield* here
}
console.log([...oddsAndEvens(6)]);
// [1, 3, 5, 2, 4, 6]
Solution
function* odds(n) {
for (let i = 1; i <= n; i += 2) yield i;
}
function* evens(n) {
for (let i = 2; i <= n; i += 2) yield i;
}
function* oddsAndEvens(n) {
yield* odds(n);
yield* evens(n);
}
console.log([...oddsAndEvens(6)]);
// [1, 3, 5, 2, 4, 6]
Create a class `NumberRange` with `constructor(start, end)`. Add `[Symbol.iterator]()` as a generator method so instances work with `for...of`, spread, and destructuring.
class NumberRange {
constructor(start, end) {
this.start = start;
this.end = end;
}
*[Symbol.iterator]() {
// yield values here
}
}
const r = new NumberRange(1, 5);
console.log([...r]); // [1, 2, 3, 4, 5]
const [a, b] = r;
console.log(a, b); // 1 2
for (const n of r) console.log(n); // 1 2 3 4 5
Solution
class NumberRange {
constructor(start, end) {
this.start = start;
this.end = end;
}
*[Symbol.iterator]() {
for (let i = this.start; i <= this.end; i++) {
yield i;
}
}
}
const r = new NumberRange(1, 5);
console.log([...r]); // [1, 2, 3, 4, 5]
const [a, b] = r;
console.log(a, b); // 1 2
for (const n of r) console.log(n);
Build a pipeline of three generators: `naturals()` — infinite: 1, 2, 3, ...; `take(gen, n)` — takes first n values from a generator; `squares(gen)` — maps each value to its square. Print the first 5 perfect squares using `take(squares(naturals()), 5)`.
function* naturals() {
let n = 1;
while (true) yield n++;
}
function* take(gen, n) {
// yield first n values from gen
}
function* squares(gen) {
// yield x*x for each x in gen
}
console.log([...take(squares(naturals()), 5)]);
// [1, 4, 9, 16, 25]
Solution
function* naturals() {
let n = 1;
while (true) yield n++;
}
function* take(gen, n) {
let count = 0;
for (const val of gen) {
if (count++ >= n) break;
yield val;
}
}
function* squares(gen) {
for (const x of gen) yield x * x;
}
console.log([...take(squares(naturals()), 5)]);
// [1, 4, 9, 16, 25]
Write a generator `countdown(n)` that yields n, n-1, ..., 1, and then returns `'Done!'`. Show that the return value appears in the final `.next()` call as `{ value: 'Done!', done: true }` but is NOT yielded by `for...of`.
Create a `LinkedList` class where each node has `value` and `next`. Add `[Symbol.iterator]()` so the list can be spread into an array and destructured. Build a list 1 → 2 → 3 and verify `[...list]` gives `[1, 2, 3]`.
class LinkedList {
constructor() { this.head = null; }
push(value) {
this.head = { value, next: this.head };
return this;
}
*[Symbol.iterator]() {
// traverse from head
}
}
const list = new LinkedList();
list.push(3).push(2).push(1);
console.log([...list]); // [1, 2, 3]
const [first, second] = list;
console.log(first, second); // 1 2
Write an async generator `asyncRange(start, end, delay)` that yields numbers from `start` to `end`, waiting `delay` ms between each. Consume it with `for await...of` inside an async `main()` function.
async function* asyncRange(start, end, delay) {
for (let i = start; i <= end; i++) {
await new Promise(r => setTimeout(r, delay));
yield i;
}
}
async function main() {
// use for await...of here
}
main();
Solution
async function* asyncRange(start, end, delay) {
for (let i = start; i <= end; i++) {
await new Promise(r => setTimeout(r, delay));
yield i;
}
}
async function main() {
for await (const n of asyncRange(1, 4, 50)) {
console.log(n); // 1 2 3 4 (50ms apart)
}
}
main();
const deck = {
suits: ['spade', 'heart', 'diamond', 'club'],
values: ['A', '2', '3'],
*[Symbol.iterator]() {
for (const value of this.values) {
for (const suit of this.suits) {
yield `${value} ${suit}`;
}
}
}
};
console.log([...deck].length); // 12
console.log([...deck][0]); // 'A spade'
console.log([...deck][11]); // '3 club'
No split tab
Cookie preferences
We use necessary cookies to run the site. With your permission, we can also save your site preferences and use analytics and advertising cookies to understand usage and support the project.
Open tools in tabs.Exercises, IDE tools, and trainers stay available as site tabs.
Switch without losing context.Move between explanations, code, and utilities while keeping your place.
Use the sidebar as your map.The left panels hold navigation, settings, files, libraries, and tool controls.
PythonJavaScriptSQLite
One IDE, three practical modes
Python in the browser.Run small scripts, try libraries, and practice API requests without installing anything.
JavaScript for quick experiments.Test browser-friendly code and compare ideas next to your learning materials.
SQLite for data practice.Open the database explorer to inspect tables, write queries, and learn SQL workflows locally.
TopicIDE
Work side by side with split tabs
Keep instructions visible.Open an exercise or reference page beside the IDE instead of jumping back and forth.
Compare tools while you learn.Place regex checks, explanations, and code experiments next to each other when the task needs it.
Close the split when you are done.The workspace returns to a single focused tab, and your open site tabs remain available.
Code Typing Trainer
Or plain text
This trainer is designed for a physical keyboard.Open this section on a laptop or desktop with a wide screen. Touch typing practice will not work correctly on a phone.
Speed: 0 chars/min
0 words/min
Best speed (60s): 0 chars/min
0 words/min
Errors: 0
Total time: 0.0 s
To practice touch typing, avoid looking at your physical keyboard.