range has always worked over slices, arrays, maps, strings, channels, and (since Go 1.22) integers. Go 1.23 (released August 2024) added range-over-func: a for range loop can iterate over a function matching one of a few specific signatures, letting you define a custom, lazy sequence — skipping items, stopping early, or generating values on demand — without first materializing a slice.
range over integers (Go 1.22)
A smaller but handy addition: since Go 1.22, ranging over a plain integer counts from 0 up to (not including) that number, a shorthand for the classic counted for loop.
for i := range 5 {
fmt.Println(i) // 0, 1, 2, 3, 4
}range-over-func: custom iterators (Go 1.23)
A function can be ranged over if it matches one of the shapes func(yield func() bool), func(yield func(V) bool), or func(yield func(K, V) bool). Inside, you call yield with each value to produce; yield returns false if the loop stopped early (a break), letting the iterator clean up.
func Evens(max int) func(func(int) bool) {
return func(yield func(int) bool) {
for i := 0; i < max; i += 2 {
if !yield(i) {
return // consumer broke out of the loop early
}
}
}
}
for n := range Evens(10) {
fmt.Println(n) // 0, 2, 4, 6, 8
if n == 4 {
break // yield(6) would return false here
}
}The new iter package and standard library support
Go 1.23 added the iter package, defining the standard iter.Seq[V] and iter.Seq2[K, V] function types for iterators, and the slices and maps standard library packages gained iterator-returning functions (like maps.Keys, slices.Values) that plug directly into range-over-func, so custom collection types can now support idiomatic for range without exposing their internals as a slice first.