Implementing iterators

October 16, 2025

The last few weeks I’ve been talking about different iterator patterns, but from the perspective of consuming iterators. Let’s switch angles now, and begin talking about how to implement iterators.

To illustrate, let’s use a simple example of a grep utility. It will read an io.Reader, and return any lines that match a regular expression input. Here’s a simple implementation, that returns all results at once (no iteration):

func grep(r io.Reader, pattern string) ([]string, error) {
  re, err := regexp.Compile(pattern)
  if err != nil {
    return nil, err
  }
  scanner := bufio.NewScanner(r)
  var matches []string
  for scanner.Scan() {
    line := scanner.Text()
    if re.MatchString(line) {
      matches = append(matches, line)
    }
  }
  if err := scanner.Err(); err != nil {
    return nil, err
  }
  return matches, nil
}

And as expected, this works fine for small inputs, but would not be ideal for large inputs, or if you want to stop processing after the first N results, for example… this is where iterators are quite useful, and we’ll be looking at re-implementing this using different iterator patterns for the rest of this series.


Share this

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

Unsure? Browse the archive .

Related Content


Iterator callbacks

Today I want to expand with a thought I touched on yesterday: Callbacks with iterators. Yesterday’s context was error handling. But I’ve found callbacks with iterators to be very valuable in a slightly different case. To understand the problem callbacks have helped me solve, we need to move away from our grep example to something a bit more involved. Let’s imagine we’re querying a REST API, which returns paginated results. A typical response body might look something like this, in JSON:


Alternatives to iter.Seq3

Last time we modified our range-over-func iterator to return a iter.Seq2, so that it could include a possible error value for each iteration. But this isn’t the only way to handle errors with range-over-func. And in fact, in some cases, it may not even be possible! Suppose you’re ranging over a key/value pair, for example. There is no iter.Seq3 option to return three values per iteration. To illustrate, let’s update our grep to return the line number, and the matching line:


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