In an iter.Seq iterator you wrote, what must happen when the yield callback returns false, and what if it keeps going?
answer
- false means the consumer is finished
- stop producing, do not merely skip
- return, so deferred cleanup runs now
- calling yield again panics at run time
basics
~20 sA false result from yield means the consuming loop has stopped. The iterator must produce no further values and return promptly, running its deferred cleanup. Calling yield again after it returned false is a run-time panic.
solid answer
~50 sThe bool that `yield` returns is the only channel the consumer has back to the producer, and false means "I am done, stop". Every call site therefore looks like `if !yield(v) { return }`. Returning at once matters twice over: it releases whatever the iterator was holding, because the closure's deferred calls run as its frame unwinds, and it stops work the caller will never see — no fetching the next page, no opening the next file. If the iterator ignores the false and calls `yield` again, the compiler-generated yield panics at run time, so a lazily-written iterator turns a caller's harmless `break` into a crash. A false result is not an error: it usually means the caller found what it was looking for, so do not convert it into one or log it as a failure.
code
go · 5 linesreturn func(yield func(string) bool) {
for _, e := range entries {
yield(e.Name()) // BUG: result ignored
}
}go deeper
Remember one rule: check what yield returns and return immediately when it is false. Know that a caller's break is what turns it false.
Explain that false means the consuming loop is finished, that continuing to call yield is a run-time panic, and that returning promptly is what releases anything the iterator holds.
Spot it in review: an ignored bool turns a caller's harmless break into wasted work or a crash, and it passes every test that consumes the whole sequence. Say how a break-early test catches it.
Frame it as a contract your published iterators must honour, since importing teams will break out of loops you never see. Make honouring early exit a review rule rather than a style preference.
## The one-bit back-channel A push iterator inverts control: your function runs the loop and the consumer's loop body is a callback you call. That leaves one problem — how does the consumer say "stop"? The answer is the single bool that `yield` returns. `true` means keep going; `false` means the consuming loop is over and will not accept another value. So every yield in a correctly written iterator is guarded: ``` if !yield(v) { return } ``` There is no other stop mechanism. No cancellation token, no error, no sentinel value. ## What makes it false The compiler builds `yield` out of the caller's loop body. It returns true after an ordinary iteration and false once the body leaves the loop — the caller executed `break`, `return`ed from the enclosing function, jumped out with `goto`, or the loop otherwise ended. From the producer's side you do not care which: false is false, and it is not an error condition. A caller who scans a directory for the first match and breaks is using the sequence exactly as intended. ## Why "return at once" is stronger than "stop yielding" Two separate things go wrong if you merely stop calling `yield` but carry on working. **Wasted or harmful work.** The point of a lazy sequence is that a caller who stops early pays for one element rather than ten thousand. An iterator that keeps reading directories, decoding rows or issuing requests after being told to stop silently deletes that benefit, and in a walk over a large tree the caller's `break` stops meaning anything at all. **Delayed cleanup.** The deferred calls inside the closure only run when the closure returns. If the sequence holds a file open for the life of the loop, that file stays open until you actually return. Returning immediately is what makes `defer` inside the iterator a real cleanup guarantee rather than an eventual one. ## The panic Go does not treat ignoring the bool as a style issue. Once the loop body has finished, the generated `yield` remembers that fact, and a further call panics with a run-time error. This is deliberate: an iterator that keeps pushing values into a loop body that has already exited would run that body after the loop — arbitrary code in an undefined state — so the runtime refuses. The practical consequence is a defect that hides in testing. An iterator whose bool is ignored works perfectly for every caller that consumes the whole sequence, and panics for the first caller who writes `break`. That is why the standard test for a hand-written iterator ranges over it with an early `break` and asserts that nothing exploded and that the iterator did no work afterwards. ## The two ways it is written wrongly ``` for _, e := range entries { yield(e.Name()) // BUG: result discarded } ``` and the subtler one: ``` for _, e := range entries { if !yield(e.Name()) { break // leaves the loop but keeps the func running } } log.Print("walk finished") // now a lie, and still work after the stop ``` The second at least avoids the panic, but if anything after the loop calls `yield` again, or performs work the caller has already opted out of, the contract is still broken. `return` is the right verb; `break` is only safe if nothing meaningful follows the loop. ## Forwarding the stop through a wrapper Most real iterators wrap another one — a filter, a limit, a mapper. The stop has to propagate outward one layer at a time: ``` func OnlyDirs(seq iter.Seq2[string, fs.DirEntry]) iter.Seq2[string, fs.DirEntry] { return func(yield func(string, fs.DirEntry) bool) { for p, d := range seq { if !d.IsDir() { continue } if !yield(p, d) { return } } } } ``` The `return` leaves the wrapper's own range loop over `seq`, which makes the inner iterator's yield return false, which makes *it* return — the stop travels down the chain by exactly the same mechanism at every level. Nothing special is required as long as each layer obeys the same rule. ## What a reviewer looks for Three things, in order: is every `yield` call's result tested; is the reaction a `return` rather than a `continue` or a bare `break`; and is there anything after the loop that would still run when the consumer has already walked away. Those three checks catch nearly every push-iterator bug that is not about resources.
- Your iterator wraps another sequence and filters it. How does a stop propagate to the inner one?Range over the inner sequence inside your closure, and when your own `yield` returns false, `return`. Leaving that inner range loop makes the inner iterator's yield return false, so it stops and returns too. The stop travels down the chain one layer at a time; no extra plumbing is needed as long as every layer honours the bool.
- Does a false result from yield mean something went wrong?No. It usually means the consumer got what it needed and broke out, which is the whole point of a lazy sequence. Do not log it as a failure, do not yield an error afterwards, and do not retry. Just release what you are holding and return.
- Why does the runtime panic instead of ignoring the extra yield call?Because the generated yield *is* the caller's loop body. Calling it after the loop has exited would execute that body outside its loop, after the surrounding function may already have returned. There is no safe behaviour to fall back on, so the run-time panics rather than run arbitrary code in an undefined state.
saying these in an interview costs you the question
- Writes yield(v) as a statement and discards the bool
- Treats a false result as an error to report or retry
- Uses continue instead of return when yield says false
- Keeps fetching or opening the next item after being told to stop
- Assumes extra yield calls after a false are silently dropped