What does `iter.Seq2[K, V]` yield, and when would you return one instead of `iter.Seq`?
answer
- two values, one yield call
- the pair need not be map-like
- yield func(K, V) bool
- the loop may drop the second variable
basics
~20 siter.Seq2[K, V] is func(yield func(K, V) bool): each yield call delivers two related values at once. Return one when every element has a companion the caller would otherwise have to reconstruct — an index, a key, a page number.
solid answer
~50 s`iter.Seq2[K, V]` is declared as `func(yield func(K, V) bool)`, so its callback takes two parameters and both values arrive from a single `yield` call. Despite the type-parameter names, the first value is not required to be a key: nothing constrains it to be comparable, unique or ordered. It is simply whatever the producer thinks belongs with each element — an index and a record, a filename and its size, a page number and the decoded page from a paginated API. You choose `Seq2` over `Seq` when the companion value is information the consumer cannot cheaply recover on its own; if it can, one value keeps the API smaller. On the consuming side a `for range` over a `Seq2` may declare two iteration variables, one, or none — dropping the second changes nothing about the iterator, which still calls `yield` with both values every time.
code
go · 16 linesfunc logPages(pages iter.Seq2[int, map[string]any]) int {
n := 0
for num, page := range pages {
fmt.Println(num, len(page))
n++
}
return n
}
func countPages(pages iter.Seq2[int, map[string]any]) int {
n := 0
for range pages { // both values dropped
n++
}
return n
}go deeper
Recall the shape: Seq2's callback is func(K, V) bool, so each iteration gives you two values from one call. Know that you can write for k, v := range over it.
Explain that K is only a name — no comparable, unique or ordered requirement — and that the loop may bind two, one, or zero variables without changing what the iterator does.
Show judgment about when the second value earns its place in a signature, and why three related values belong in a struct yielded from a plain Seq rather than in a wider iterator type.
Own the API-shape call: the pairing you publish is hard to change once other teams range over it. Decide early whether the companion value is part of the contract or an implementation detail callers should not depend on.
## The declaration ```go type Seq2[K, V any] func(yield func(K, V) bool) ``` Compare it with the single-value form, `type Seq[V any] func(yield func(V) bool)`. The only difference is the arity of the callback. Everything else — that an iterator is a function, that it pushes values into `yield`, that `yield`'s `bool` result says whether to keep going — is identical. Both values are delivered by **one** call. `Seq2` is not two sequences advanced side by side, and there is no moment at which the first value exists without the second. ## `K` is a name, not a constraint The type parameters are declared `[K, V any]`. `K` is *not* `comparable`, so the first value can be a slice, a func, a struct containing a map — anything. And nothing requires the first values to be unique across a traversal, sorted, or to start at zero. Reading `Seq2` as "a sequence of map entries" is the most common misconception about it. What the type really encodes is: *these two things belong together for each element.* Some honest pairings: - index and element, when position matters to the consumer - key and value, when walking something map-shaped - page number and decoded page body, when an SDK exposes a paginated endpoint - name and size, when walking a directory ## Choosing between `Seq`, `Seq2` and a struct Ask what the consumer would have to do if you handed back only one value. If they would immediately write a counter, or look the companion up again, the second value belongs in the signature — that is a `Seq2`. If they would ignore it, it does not. Once the companion grows beyond one value, stop. There is no `Seq3`, deliberately. Three or more related values want a named struct type yielded from a plain `Seq`, which is also the better choice when the group deserves methods or its own documentation. `Seq2` is the compromise for the very common two-value case, where a struct would force both sides to declare and unpack a type for no gain. A `Seq2` whose second value is an `error` is also a recognised shape, though how a sequence should surface failures is a design discussion of its own. ## Consuming a `Seq2` All three of these are legal over `seq` of type `iter.Seq2[int, string]`: ```go for n, s := range seq { … } for n := range seq { … } // second value not bound for range seq { … } // neither value bound ``` Dropping a variable is a property of the loop, not of the iterator. The generated `yield` still has signature `func(int, string) bool` and still receives both values on every call; the loop body simply does not name one. There is no cheaper path through the iterator when you only want the first value, and no way to ask it to skip computing the second. The reverse is not true: you cannot range a single-value `iter.Seq[V]` with two iteration variables. The compiler matches the loop's variable count against the callback's arity, and asking for two values from a `func(func(V) bool)` does not compile. ## A worked shape An SDK wrapping a paginated endpoint has a natural pairing. Each unit of work is one HTTP response containing a JSON page, and the page number is genuinely useful to the caller — for logging, for resuming, for a progress bar — but the caller cannot derive it without counting. So the method returns `iter.Seq2[int, map[string]any]`: the page number and the decoded body, delivered together, one call per page. Had the SDK returned `iter.Seq[map[string]any]`, every caller who wanted to log "failed on page 7" would maintain their own counter, and every one of them would get the off-by-one wrong in a slightly different way. ## What to say in an interview "`Seq2[K, V]` is `func(yield func(K, V) bool)` — two values from one `yield` call, and `K` is just a name, not a key constraint. I reach for it when the second value is something the consumer would otherwise have to reconstruct, like an index or a page number. Consumers can declare two, one, or zero iteration variables; that only affects the loop, not what the iterator computes. Past two values, I yield a struct from a plain `Seq`."
- For an `iter.Seq2[int, string]` named seq, is `for n := range seq` legal?Yes. A range over a two-value iterator may declare two iteration variables, one, or none. With one, you bind the first value and the second is simply not named. The iterator is unaffected — its `yield` still takes both parameters and is still called with both on every iteration.
- Does `iter.Seq2` require the first value to be a unique key?No. `K` and `V` are ordinary type-parameter names declared `any`, so the first value need not be comparable, unique or ordered. The type only says that two things travel together per element — a page number and a body, an offset and a record, a name and a size.
- Why is there no `iter.Seq3`?Because at three values a named struct is the better tool: it can be documented, given methods, and extended without changing every signature. `Seq2` exists only because the two-value case — index and element, key and value — is common enough that forcing a pair type on both sides would be pure friction.
saying these in an interview costs you the question
- Says Seq2 always yields a map key and value
- Thinks Seq2 is two sequences consumed in parallel
- Claims the loop must declare both iteration variables
- Believes yield is called once per value rather than per pair
- Assumes K must satisfy the comparable constraint