Range over func

October 8, 2025

Since Go 1.23, we’ve had a new way we can implement iterators. I’ve written previously about it if you’re interested. But today we’ll take a glance at how it affects our Orders example:

orders, err := db.Orders(ctx, userID)
if err != nil {
	return err
}
for order, err := range orders {
	if err != nil {
		return err
	}
	/* Do something with each order */
}

Unless you’re already quite familiar with the range-over-func feature, it’s probably not immediately clear what’s going on here, just by looking at the code. So let’s dissect a bit.

The function signature of db.Orders looks something like this:

func (db *DB) Orders(ctx context.Context, uerID int) iter.Seq2[Order, error]

Ehh… what’s that iter.Seq2 thing? Let’s look at the GoDoc:

type Seq2

type Seq2[K, V any] func(yield func(K, V) bool)

Seq2 is an iterator over sequences of pairs of values, most commonly key-value pairs. When called as seq(yield), seq calls yield(k, v) for each pair (k, v) in the sequence, stopping early if yield returns false. See the iter package documentation for more details.

Contrary to the claim that Seq2 “most commonly” iterates over key-value pairs, I find myself using it for this type of iterator quite a lot, which I guess we could call “value-error pairs”.

But what magic does this contain? For a detailed explanation, I recommend Zach Musgrave’s excellent blog post Go range iterators demystified. If that’s too much of a bother to read, though, the short version is: An iter.Seq2 is a function type that accepts a yield callback (of type func(K, V) bool) which is to be called for each pair in the iteration list, or until it returns false. The Go language itself ties that directly to the range keyword, for some handy iteration as we saw above!

We’ll look in more detail at some of the nuances of this approach in the upcoming days.


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Get daily content like this in your inbox!

Subscribe