Separating iteration from advancement

November 3, 2025

Last week we looked at a simple custom iterator pattern:

type Result struct {/* ... */}
func (*Result) Next() (string, bool)
func (*Result) Err() error

But let’s talk about a few variations, and when they might make sense.

First, I already mentioned last week that in some cases we could simply eliminate the bool value if the zero value of the iterated value can serve as an indication that iteration has completed. This is most common with pointer types. For example:

func (*Result) Next() *User

But there are other patterns to consider, as well. One of the most popular is to have a Next() method that only advances to the next item, leaving processing of the item to other method(s). A common example can be found in the database/sql package:

This pattern is useful in a few main situations:

  1. You need to report a per-item error, as distinct from an iterator-general error. This is the case in the database package. Scan() can return an error, without aborting the iterator.
  2. You have different ways of processing an item. This only half way applies to database/sql. In addition to the Scan method, the *Rows type also exposes ColumyTypes, and Columns, and NextResultSet. These are for advanced use well beyond the scope of today’s discussion, but the custom iterator pattern grants this flexiblity, and that’s what we care about here.
  3. Probably less common, but you may want to not process some items. If you know you’ll discard an item, you may be able to save some CPU and memory by not parsing it at all, and advancing directly to the next item.

Share this

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

Unsure? Browse the archive .

Related Content


Building a custom iterator

We’ve looked at using channels as iterators, and found they’re hardly ideal. Let’s look at the next obvious answer: custom iterators. Before range-over-func, which we’ll get to next, custom iterators were really the only meaningful solution. And they still remain a very viable one, because of their great flexibility. Let’s start with a simple implementation of a custom iterator version of our grep function (see it in the playground): type Result struct { re *regexp.


Custom iterators

Until recently, custom iterators were probably the most common way to iterate over a list of elements that might trigger an error. Several examples exist in the standard library. Perhaps the most well known would be the sql.Rows type, which provides (among others), the following methods: Next() bool — Advances to the next item Scan(...any) error — Processes the current item Err() error — Reports an iteration error Close() error — Closes iterator, possibly before the last item has been processed These four methods are pretty standard, in any custom iterator implementation, though Scan() will typically be replaced with an implementation-specific method to process the current result, and in some cases Err() and Close() may be combined.


Handling errors during iteration with range-over-func

Yesterday we looked at a range-over-func iterator that was missing a vital piece: Error handling during iteration. I know of three possible solutions to this problem, and today we’ll look at the simplest of them, which I typically recommend: Using iter.Seq2. We’ve already looked at this pattern from the consumer’s perspective. Today we’ll see how the implementation works. func grep(r io.Reader, pattern string) (iter.Seq2[string, error], error) { re, err := regexp.

Get daily content like this in your inbox!

Subscribe