Fundamentals 34 min read

Mastering Go 1.23 Iterators: From Basics to Advanced Usage

This article explores the newly introduced iterator feature in Go 1.23, explaining what iterators are, why they were added, how to implement them manually, how to use the new iter package, and how the slices and maps packages provide iterator‑friendly utilities for Go developers.

Go Programming World
Go Programming World
Go Programming World
Mastering Go 1.23 Iterators: From Basics to Advanced Usage

What is an iterator

According to Wikipedia, an iterator is an object that enables a user to traverse a container (such as a linked list or array) without needing to know the memory allocation details of the container. Its behavior is similar to a cursor in database technology, and the concept first appeared in the CLU language in 1974.

An iterator (English: iterator) is an object that allows a user to traverse a container object (container, e.g., a linked list or array). The designer can use this interface without worrying about the implementation details of the container's memory allocation. Its behavior is similar to a cursor in database technology. The iterator first appeared in the CLU programming language designed in 1974.
Various languages implement iterators differently. Some object‑oriented languages like Java, C#, Ruby, Python, and Delphi have built‑in iterator support, which we call implicit iterators. Languages like C++ do not have iterator as a language feature, but the STL provides powerful iterator functionality via templates. The STL container's memory address may be reallocated, yet an iterator bound to the container can still locate the correct address after reallocation.

Although the definition above is a bit obscure, it shows that many mainstream languages natively support iterators, and Go finally added iterator support in version 1.23, marking a major syntactic update since generics.

Go's official definition of an iterator in the iter package documentation is:

An iterator is a function that passes successive elements of a sequence to a callback function, conventionally named yield . The function stops either when the sequence is finished or when yield returns false, indicating early termination.

Translated:

An iterator is a function that passes successive elements of a sequence to a callback function (usually called yield ). The function stops when the sequence ends or when yield returns false (meaning stop early).

Now you just need to remember that an iterator is a function; the rest of the article will help you master Go iterators.

Why introduce iterators

In the discussion discussions/56413, Go team lead rsc explained why iterators are needed:

In the standard library alone, we have many functions like archive/tar.Reader.Next , bufio.Reader.ReadByte , bufio.Scanner.Scan , container/ring.Ring.Do , database/sql.Rows , expvar.Do , flag.Visit , go/token.FileSet.Iterate , path/filepath.Walk , runtime.Frames.Next , and sync.Map.Range . Hardly any of them agree on the exact details of iteration. Even when the signatures agree, the semantics often differ. This raises the learning cost for Go code and conflicts with Go's engineering‑focused simplicity.

Thus the iterator feature, led by rsc, was finally landed in Go 1.23.

When iterators were introduced

The iterator proposal first appeared in August 2020 ( issues/40605) and went through many discussions ( issues/43557, discussions/54245, discussions/56413, issues/61405, issues/61897). It was experimentally added to Go 1.22 and enabled with the GOEXPERIMENT=rangefunc compile flag. The proposal for the golang.org/x/exp/xiter package was later withdrawn.

Go 1.23 finally released the iterator feature in August 2024, adding the iter package for user‑defined iterators.

Changes

Originally, Go's for‑range could iterate over arrays, slices, strings, maps, channels, and integers. In Go 1.22, for i := range 10 was added for integer values. In Go 1.23, for‑range now supports three specific function types:

// No‑return iterator: function f signature is func(func() bool)
for range f { ... }

// Single‑value iterator: function f signature is func(func(V) bool)
for x := range f { ... }

// Two‑value iterator: function f signature is func(func(K, V) bool)
for x, y := range f { ... }

Now for‑range can iterate over any type that implements one of these signatures.

Iterator examples

Below are several hand‑crafted iterator implementations.

Iterator pattern

package main

import "fmt"

type Iterator struct {
    data  []int
    index int
}

func NewIterator(data []int) *Iterator {
    return &Iterator{data: data, index: 0}
}

func (it *Iterator) HasNext() bool {
    return it.index < len(it.data)
}

func (it *Iterator) Next() int {
    if !it.HasNext() {
        panic("Stop iteration")
    }
    value := it.data[it.index]
    it.index++
    return value
}

func main() {
    it := NewIterator([]int{0, 1, 2, 3, 4})
    for it.HasNext() {
        fmt.Println(it.Next())
    }
}

This is a simplified iterator that provides HasNext and Next methods.

Callback‑style iterator

package main

import "fmt"

func generator(n int) <-chan int {
    ch := make(chan int)
    go func() {
        for i := 0; i < n; i++ {
            ch <- i
        }
        close(ch)
    }()
    return ch
}

func main() {
    for n := range generator(5) {
        fmt.Println(n)
    }
}

This uses a channel to generate values lazily.

Simple iterator (no return value)

package main

import "fmt"

type Seq0 func(yield func()) bool

func iter0[Slice ~[]E, E any](s Slice) Seq0 {
    return func(yield func()) bool {
        for range s {
            if !yield() {
                return false
            }
        }
        return true
    }
}

func main() {
    s1 := []int{1, 2, 3}
    i := 0
    for range iter0(s1) {
        fmt.Printf("i=%d
", i)
        i++
    }
}

Here Seq0 represents an iterator that yields no values; the loop body runs for each iteration.

Iterator that yields one value

package main

import "fmt"

type Seq1[V any] func(yield func(V)) bool

func iter1[Slice ~[]E, E any](s Slice) Seq1[E] {
    return func(yield func(E)) bool {
        for _, v := range s {
            if !yield(v) {
                return false
            }
        }
        return true
    }
}

func main() {
    s1 := []int{1, 2, 3}
    for v := range iter1(s1) {
        fmt.Printf("v=%d
", v)
    }
}

Iterator that yields two values (index and element)

package main

import "fmt"

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

func iter2[Slice ~[]E, E any](s Slice) Seq2[int, E] {
    return func(yield func(int, E)) bool {
        for i, v := range s {
            if !yield(i, v) {
                return false
            }
        }
        return true
    }
}

func main() {
    s1 := []int{1, 2, 3}
    for i, v := range iter2(s1) {
        fmt.Printf("%d=%d
", i, v)
    }
}

The iter package

The new iter package defines two generic function types:

type Seq[V any] func(yield func(V)) bool

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

These correspond to the iterator signatures we have been implementing manually. The package also provides helper functions Pull and Pull2 to pull values from an iterator, which will be explained later.

The slices package

The slices package adds iterator‑friendly functions:

All : returns an iterator over index‑value pairs in the usual order.

Values : returns an iterator over the slice elements.

Backward : returns an iterator that traverses the slice from the end to the beginning.

Collect : collects iterator values into a new slice.

AppendSeq : appends iterator values to an existing slice.

Sorted , SortedFunc , SortedStableFunc : collect and sort iterator values.

Chunk : returns an iterator over consecutive sub‑slices of at most n elements.

Source code (simplified):

package slices

import (
    "cmp"
    "iter"
)

// All returns an iterator over index‑value pairs in the usual order.
func All[Slice ~[]E, E any](s Slice) iter.Seq2[int, E] {
    return func(yield func(int, E) bool) {
        for i, v := range s {
            if !yield(i, v) {
                return
            }
        }
    }
}

// Backward returns an iterator over index‑value pairs traversing backward.
func Backward[Slice ~[]E, E any](s Slice) iter.Seq2[int, E] {
    return func(yield func(int, E) bool) {
        for i := len(s) - 1; i >= 0; i-- {
            if !yield(i, s[i]) {
                return
            }
        }
    }
}

// Values returns an iterator that yields the slice elements in order.
func Values[Slice ~[]E, E any](s Slice) iter.Seq[E] {
    return func(yield func(E) bool) {
        for _, v := range s {
            if !yield(v) {
                return
            }
        }
    }
}

// ... other functions omitted for brevity ...

The maps package

The maps package provides similar iterator utilities for maps:

All : iterator over key‑value pairs.

Keys : iterator over keys.

Values : iterator over values.

Insert : inserts iterator key‑value pairs into an existing map.

Collect : collects iterator key‑value pairs into a new map.

Source code (simplified):

package maps

import "iter"

// All returns an iterator over key‑value pairs from m.
func All[Map ~map[K]V, K comparable, V any](m Map) iter.Seq2[K, V] {
    return func(yield func(K, V) bool) {
        for k, v := range m {
            if !yield(k, v) {
                return
            }
        }
    }
}

// Keys returns an iterator over keys in m.
func Keys[Map ~map[K]V, K comparable, V any](m Map) iter.Seq[K] {
    return func(yield func(K) bool) {
        for k := range m {
            if !yield(k) {
                return
            }
        }
    }
}

// Values returns an iterator over values in m.
func Values[Map ~map[K]V, K comparable, V any](m Map) iter.Seq[V] {
    return func(yield func(V) bool) {
        for _, v := range m {
            if !yield(v) {
                return
            }
        }
    }
}

// ... other functions omitted for brevity ...

Using the iterator utilities

package main

import (
    "fmt"
    "maps"
    "slices"
)

func main() {
    s := []string{"a", "b", "c"}
    for i, v := range slices.All(s) {
        fmt.Printf("%d => %s
", i, v)
    }

    m := map[string]int{"a": 0, "b": 1, "c": 2}
    for k, v := range maps.All(m) {
        fmt.Printf("%s: %d
", k, v)
    }
}

This demonstrates that both slices and maps can be iterated uniformly using the All function, simplifying the learning curve for Go developers.

Extended Reading

Wiki/Iterator: https://zh.wikipedia.org/wiki/迭代器

Go Wiki: Rangefunc Experiment: https://go.dev/wiki/RangefuncExperiment

Go Wiki: Range Clauses: https://go.dev/wiki/Range

The Go Programming Language Specification – For statements with range clause: https://go.dev/ref/spec#For_range

Range over int: https://groups.google.com/g/golang-nuts/c/7J8FY07dkW0

Range Over Function Types: https://go.dev/blog/range-functions

Go 1.23 Release Notes: https://go.dev/doc/go1.23

Go 1.22 Release Notes: https://go.dev/doc/go1.22

iter Documentation: https://pkg.go.dev/[email protected]

slices Documentation: https://pkg.go.dev/[email protected]

maps Documentation: https://pkg.go.dev/[email protected]

Why People are Angry over Go 1.23 Iterators: https://www.gingerbill.org/article/2024/06/17/go-iterator-design/

proposal: Go 2: iterators #40605: https://github.com/golang/go/issues/40605

proposal: Go 2: function values as iterators #43557: https://github.com/golang/go/issues/43557

discussion: standard iterator interface #54245: https://github.com/golang/go/discussions/54245

user‑defined iteration using range over func values #56413: https://github.com/golang/go/discussions/56413

spec: add range over int, range over func #61405: https://github.com/golang/go/issues/61405

iter: new package for iterators #61897: https://github.com/golang/go/issues/61897

proposal: x/exp/xiter: new package with iterator adapters #61898: https://github.com/golang/go/issues/61898

src/cmd/compile/internal/rangefunc/rewrite.go: https://github.com/golang/go/blob/go1.23.0/src/cmd/compile/internal/rangefunc/rewrite.go

Coroutines for Go: https://research.swtch.com/coro

Coroutines for Go (HN discussion): https://news.ycombinator.com/item?id=36762682

Go 1.23 custom iterators and iter package: https://tonybai.com/2024/06/24/range-over-func-and-package-iter-in-go-1-23/

Python iterators: https://docs.python.org/zh-cn/3.13/tutorial/classes.html#iterators

JavaScript iterators and generators: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Iterators_and_generators

A look at iterators in Go: https://medium.com/eureka-engineering/a-look-at-iterators-in-go-f8e86062937c

Article source code: https://github.com/jianghushinian/blog-go-example/tree/main/iterator

Permanent article URL: https://jianghushinian.cn/2025/07/17/go-iterator/

Contact Me

Public account: Go Programming World

WeChat: jianghushinian

Email: [email protected]

Blog: https://jianghushinian.cn

GitHub: https://github.com/jianghushinian

IteratorsprogrammingGoGenericsslicesItermaps
Go Programming World
Written by

Go Programming World

Mobile version of tech blog https://jianghushinian.cn/, covering Golang, Docker, Kubernetes and beyond.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.