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.
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)) boolThese 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
Go Programming World
Mobile version of tech blog https://jianghushinian.cn/, covering Golang, Docker, Kubernetes and beyond.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
