Generators (yield)
Reviewed & published by Brayan K
Need to loop over a million rows, or the lines of a huge file, without exhausting memory? Generators produce values lazily — one at a time, on demand — using the yield keyword. They look like normal functions but sip memory instead of gulping it.
Part of the free PHP course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- Write a generator function with the yield keyword
- Understand lazy, pause-and-resume execution
- See why generators use near-constant memory
- Yield key => value pairs like an associative array
- Stream large data such as file lines one at a time
- Convert a generator to an array with iterator_to_array
1️⃣ Your First Generator
Any function containing yield becomes a generator. Calling it doesn't run the body — it returns a Generator object. Iterating with foreach runs the body until the first yield, hands that value back, and pauses. The next loop iteration resumes right where it left off. Watch the interleaved output below — it proves each value is generated only when asked for.
<?php
// A GENERATOR is a function that contains 'yield'. Instead of returning
// once, it produces a SERIES of values — one at a time, on demand.
function countTo(int $max): \Generator {
for ($i = 1; $i <= $max; $i++) {
echo " (generating $i)\n"; // proves values are made lazily
yield $i; // hand back $i and PAUSE here
}
}
// foreach drives the generator, resuming it after each yield.
foreach (countTo(3) as $n) {
echo "got $n\n";
}
?>2️⃣ Why Generators Save Memory
This is the headline benefit. Building an array of a million integers would allocate space for all million at once. A generator holds just one value at a time, so the loop below sums a million numbers while memory usage stays essentially flat. Same result, a fraction of the footprint.
<?php
// The killer feature: generators use almost NO memory, however big the range.
// A normal array of a million ints would eat megabytes; a generator holds one.
function bigRange(int $start, int $end): \Generator {
for ($i = $start; $i <= $end; $i++) {
yield $i;
}
}
$sum = 0;
foreach (bigRange(1, 1_000_000) as $n) {
$sum += $n;
}
echo "Sum 1..1,000,000 = " . $sum . "\n"; // 500000500000
echo "Peak memory stayed tiny — only one value lived at a time.\n";
?>3️⃣ Yielding Keys and Values
Just like an array, a generator can produce key => value pairs with yield $key => $value. A bare yield; produces null with auto-incrementing integer keys. Helpers like iterator_count() work on any generator.
<?php
// yield can produce KEY => VALUE pairs, just like an associative array.
function settings(): \Generator {
yield 'theme' => 'dark';
yield 'lang' => 'en';
yield 'beta' => true;
}
foreach (settings() as $key => $value) {
echo $key . ' = ' . var_export($value, true) . "\n";
}
// yield with no value produces null keys auto-numbered from 0.
function blanks(): \Generator {
yield;
yield;
}
echo "Blank count: " . iterator_count(blanks()) . "\n"; // 2
?>4️⃣ Streaming Real Data
The everyday win is streaming. Reading a multi-gigabyte log file line by line through a generator means you process one line at a time and never load the whole file. Here we simulate it with a small array, but the shape — yield each item, process it in the loop — is exactly how you'd wrap fgets() over a real file handle.
<?php
// A classic real-world use: read a huge file ONE line at a time,
// never loading the whole thing into memory.
function readLines(array $lines): \Generator {
foreach ($lines as $line) {
yield trim($line); // process and hand back each line
}
}
// (Pretend these came from a multi-gigabyte log file.)
$log = [" ERROR boom ", "INFO ok", " ERROR crash "];
$errors = 0;
foreach (readLines($log) as $line) {
if (str_starts_with($line, 'ERROR')) {
$errors++;
echo "Found: $line\n";
}
}
echo "Total errors: $errors\n";
?>Now you try — fill in each ___ using the 👉 hint, then run it and check against the Output panel.
<?php
// 🎯 YOUR TURN — fill in each blank marked ___ , then run it.
function evens(int $max): \Generator {
for ($i = 2; $i <= $max; $i += 2) {
___ $i; // 👉 the keyword that produces one value
}
}
$collected = [];
foreach (evens(8) as $n) {
$collected[] = $n;
}
echo implode(',', $collected) . "\n"; // should print: 2,4,6,8
// Turn a generator into a real array with this helper:
$arr = ___(evens(4)); // 👉 the function: generator -> array
echo count($arr) . "\n"; // should print: 2
// ✅ Expected output:
// 2,4,6,8
// 2
?>🧩 Reorder Challenge
These lines define a generator and consume it — but they're scrambled. Put them in the order that prints ab.
C → B → D → E → A
Declare the generator: open the function (C), yield 'a' then 'b' (B, D), close it (E). Then iterate (A), which resumes the body twice and prints ab.
🧠 Quick Recall — Predict the Output
1. What prints — and in what order?
start — only that. Calling g() returns a generator without running the body; 'A' wouldn't print until you start iterating.
2. What is $total?
60 — the loop pulls 10, 20, then 30 from the generator and adds them.
3. What does the second loop print?
Nothing (empty) — a generator can be consumed only once. After the first loop exhausts it, the second loop has nothing left to yield.
Common Errors (and the fix)
- "Cannot rewind a generator that was already run" — you tried to iterate it twice. Call the generator function again for a fresh one, or capture values with iterator_to_array().
- "Cannot use yield outside of a function" — yield only works inside a function body. Wrap it in one.
- Your generator body never runs — you called it but never iterated. The body executes lazily; start a foreach to drive it.
- You can't index it like $gen[2] — generators have no random access. Convert to an array first if you need indexing.
Pro Tips
- 💡 Reach for generators on big or streamed data. Files, database cursors, API pages — anywhere loading everything at once would hurt memory.
- 💡 Use yield from to delegate to another generator or iterable — it flattens nested sources cleanly.
- 💡 Need the data twice? Materialise it. iterator_to_array($gen) trades memory for re-iterability when that's the right call.
📋 Quick Reference — Generators
| Syntax | Example | What It Does |
|---|---|---|
| yield $v; | yield $i; | Produce a value, then pause |
| yield $k => $v; | yield 'a' => 1; | Produce a key/value pair |
| yield from | yield from other(); | Delegate to another iterable |
| foreach | foreach (gen() as $v) | Drive the generator |
| iterator_to_array() | iterator_to_array(gen()) | Generator → array |
| iterator_count() | iterator_count(gen()) | Count yielded values |
Mini-Challenge: Fibonacci Generator
No code is filled in this time — just a brief and an outline. Write it yourself, run it on onecompiler.com/php or your own machine, then check your result against the expected output in the comments.
<?php
// 🎯 MINI-CHALLENGE: a Fibonacci generator.
// No code is filled in — work from the steps below, then run it.
//
// 1. Write a generator function fib(int $count): Generator.
// Start $a = 0, $b = 1.
// Loop $count times: yield $a, then advance the pair with
// [$a, $b] = [$b, $a + $b];
// 2. Loop over fib(8) and collect each value into an array.
// 3. echo the values joined with commas.
//
// ✅ Expected output:
// 0,1,1,2,3,5,8,13
// your code here
?>🎉 Lesson Complete!
- ✅ Any function with yield is a generator — it produces values lazily
- ✅ Execution pauses at each yield and resumes on the next iteration
- ✅ Only one value lives in memory at a time — perfect for huge data
- ✅ You can yield key => value pairs and use yield from
- ✅ Generators iterate once; use iterator_to_array() if you need them again
Practice quiz
What keyword turns an ordinary function into a generator?
- return
- generate
- yield
- async
Answer: yield. Any function containing yield becomes a generator that produces a series of values one at a time.
What happens when you CALL a generator function (before iterating it)?
- It returns a Generator object without running the body
- Its body runs fully and returns an array
- It throws until you add a loop
- It runs up to the first yield only
Answer: It returns a Generator object without running the body. Calling a generator function does not run its body — it returns a Generator object. The body runs only when you iterate it.
For function g() { echo 'A'; yield 1; } $gen = g(); echo 'start'; what prints?
- Astart
- startA
- A1start
- start
Answer: start. Only 'start' prints. Calling g() returns a generator without running the body, so 'A' would not print until you start iterating.
What does yield do to the function's execution?
- Ends the function permanently
- Hands a value to the loop and pauses, preserving local state
- Restarts the function from the top
- Skips to the next function
Answer: Hands a value to the loop and pauses, preserving local state. yield hands the value back and freezes the function exactly there, preserving locals and the instruction pointer; it resumes after the yield on the next iteration.
Why are generators so memory-efficient for large sequences?
- Only one value lives in memory at a time
- They compress the array
- They cache to disk
- They use less CPU
Answer: Only one value lives in memory at a time. A generator holds just one value at a time instead of materialising the whole set, so memory stays nearly flat however large the range.
How many times can a generator be iterated?
- As many times as you want
- Twice
- Exactly once — it cannot be rewound
- Until you call reset()
Answer: Exactly once — it cannot be rewound. A generator can be iterated only once and cannot be rewound. Call the function again for a fresh one, or capture values with iterator_to_array().
Which helper converts a generator into a real array?
- array_from()
- iterator_to_array()
- generator_array()
- to_array()
Answer: iterator_to_array(). iterator_to_array() materialises a generator's values into an array, trading memory for re-iterability.
How do you produce a key => value pair from a generator?
- yield [$key, $value]
- return $key => $value
- yield key($value)
- yield $key => $value
Answer: yield $key => $value. Just like an array, a generator can produce pairs with yield $key => $value.
For function nums() { yield 10; yield 20; yield 30; } summed in a foreach, what is the total?
- 30
- 60
- 10
- 0
Answer: 60. The loop pulls 10, 20, then 30 from the generator and adds them: 60.
What does yield from do?
- Imports a class
- Returns early
- Delegates to another generator or iterable, flattening it
- Reverses the sequence
Answer: Delegates to another generator or iterable, flattening it. yield from delegates to another generator or iterable, cleanly flattening nested sources into the current generator.
Continue this course
- Previous: Regular Expressions (preg)
- Next: File Handling — Read, write, and append to files on the server with PHP
- Quick reference: PHP cheat sheet
Frequently asked questions
What's the difference between a generator and just returning an array?
A function that builds an array computes every value up front and holds them all in memory, then returns the whole thing. A generator uses yield to produce values one at a time, on demand, pausing between each — so only a single value exists in memory at any moment. For small fixed lists the array is fine and simpler. For large or infinite sequences (a million rows, lines of a huge file, an endless stream) a generator is dramatically more memory-efficient because it never materialises the full set.
How does yield actually work — does the function stop running?
Calling a generator function doesn't run its body at all; it returns a Generator object. The body runs only when you iterate (usually with foreach). Execution proceeds until it hits a yield, which hands that value back to the loop and freezes the function exactly there — local variables and the instruction pointer are preserved. On the next iteration the function resumes right after the yield. This pause-and-resume is what makes lazy, one-at-a-time production possible.
Can I rewind a generator or use it twice?
No. A generator can only be iterated once and cannot be rewound — once you've consumed it, it's exhausted, and trying to loop again yields nothing (or throws if you call rewind() after it has advanced). If you need to traverse the data more than once, either call the generator function again to get a fresh generator, or capture the values into an array with iterator_to_array() and iterate that array as many times as you like.
When should I NOT use a generator?
Avoid generators when you need random access by index, the full count up front, or to iterate the data multiple times — arrays do all of that and generators don't. They also add a little overhead per value, so for a small list that easily fits in memory a plain array is simpler and often faster. Reach for a generator specifically when the dataset is large, streamed, or potentially infinite, and you only need to walk through it once in order.