Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Go  Module 12 – Iterators and range-over-func

Iterators in slices, maps, strings and reflect

Use the iterator functions of slices, maps, strings and bytes, iterate a map in a fixed order, split text lazily, and walk struct fields with reflect.

  • Intermediate
  • 20 minutes
  • Examples run with Go 1.27.1 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 7 of 20 minutes free
Free: Each package names its iterators for what they walk · A map's keys have no order until slices.Sorted gives them one · strings and bytes split text one piece at a time. With a pass: 2 in-depth parts and 3 interview answers, about 13 minutes.

What you will learn

  • Use slices.All, Values, Backward, Chunk, Collect and Sorted on slices and on the sequences they yield
  • Iterate a map in a fixed order with maps.Keys and slices.Sorted, and merge maps without losing quantities
  • Split text lazily with strings.Lines, SplitSeq and FieldsSeq, and predict what each yields
  • Measure the allocations an iterator saves over a slice, and choose between iterating and collecting
  • Walk a struct's fields and a type's methods with the reflect iterators of Go 1.26

Before you start

What you’ll learn · Before you start

What you will learn

  • Use slices.All, Values, Backward, Chunk, Collect and Sorted on slices and on the sequences they yield
  • Iterate a map in a fixed order with maps.Keys and slices.Sorted, and merge maps without losing quantities
  • Split text lazily with strings.Lines, SplitSeq and FieldsSeq, and predict what each yields
  • Measure the allocations an iterator saves over a slice, and choose between iterating and collecting
  • Walk a struct's fields and a type's methods with the reflect iterators of Go 1.26

Before you start

On this page

Printing a map’s keys in order used to take a loop, an append and a sort. Since Go 1.23 it is one expression, slices.Sorted(maps.Keys(stock)), and in tour.go below it printed [atta dal rice]. The standard library now hands out iterators over slices, maps, the lines and fields of text and the fields of a struct, and takes them back into slices and maps.

You will use the iterators in slices and maps, walk a map in a fixed order, split text without building a slice, and walk a struct with reflect. Each program below ran on Go 1.27.1, and the output under it is its own.

Each package names its iterators for what they walk

The iterators in slices walk a slice: All yields each index with its element, Values the elements alone, Backward both from the end, and Chunk sub-slices of up to n elements. Package maps has All, Keys and Values. Packages strings and bytes cut text up into lines, pieces between separators and words. Package reflect, since Go 1.26, walks the fields of a struct type and the methods of any type. Functions such as Collect and Sorted go the other way: they take a sequence and build a slice or a map.

Package Returns an iterator Takes an iterator Since
slices All, Values, Backward, Chunk Collect, AppendSeq, Sorted, SortedFunc, SortedStableFunc Go 1.23
maps All, Keys, Values Collect, Insert Go 1.23
strings and bytes Lines, SplitSeq, SplitAfterSeq, FieldsSeq, FieldsFuncSeq none Go 1.24
reflect Type.Fields, Type.Methods, Type.Ins, Type.Outs, Value.Fields, Value.Methods none Go 1.26

tour.go uses one from each package:

tour.go: slices, maps, strings and reflect Go · tour.go
// tour.go: one iterator each from slices, maps, strings and reflect.
package main

import (
	"fmt"
	"maps"
	"reflect"
	"slices"
	"strings"
)

// Ticket is one booking.
type Ticket struct {
	PNR   string
	Coach string
	Paise int
}

func main() {
	queue := []string{"Asha", "Ravi", "Meena"}
	for i, name := range slices.Backward(queue) {
		fmt.Print(i, " ", name, "; ")
	}
	fmt.Println()

	stock := map[string]int{"rice": 40, "atta": 25, "dal": 12}
	fmt.Println(slices.Sorted(maps.Keys(stock)))

	for field := range strings.FieldsSeq("GET /orders 200") {
		fmt.Printf("[%s]", field)
	}
	fmt.Println()

	for f := range reflect.TypeFor[Ticket]().Fields() {
		fmt.Print(f.Name, " ", f.Type, "; ")
	}
	fmt.Println()
}

Output

2 Meena; 1 Ravi; 0 Asha; 
[atta dal rice]
[GET][/orders][200]
PNR string; Coach string; Paise int; 

Recorded with Go 1.27.1 on macOS 26 arm64. To run it yourself: go run tour.go

Backward gave Meena first, with her index 2. Sorted put the three keys in order, FieldsSeq split a request line into three words, and reflect gave each field of Ticket with its type.

Checkpoint

Check: Which package gives which iterator

  1. Question 1 of 2 What does the first step of for i, name := range slices.Backward([]string{"Asha", "Ravi", "Meena"}) give?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: i = 2, name = "Meena"

    slices.Backward yields the same pairs as slices.All, index first, in the reverse order. Meena is at index 2.

  2. Question 2 of 2 Which function yields the lines of a string, one at a time?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: strings.Lines

    The text iterators are in strings and bytes: Lines, SplitSeq, SplitAfterSeq, FieldsSeq and FieldsFuncSeq.

A map’s keys have no order until slices.Sorted gives them one

The maps documentation says the order of Keys is not specified and may differ from one call to the next. A program that prints keys straight from a map can therefore print them differently on every run. slices.Sorted collects the keys into a new slice and sorts it, so the order is the same every time. Follow the expression through its steps:

Interactive · stage by stage How slices.Sorted(maps.Keys(stock)) orders a map Step through the expression from stock.go, from the map to the sorted slice.
How slices.Sorted(maps.Keys(stock)) orders a map, step 5 of 5. Showing map[string]int, maps.Keys(stock) (nothing yielded yet), Sorted collects ([rice atta dal], say), Sorted sorts ([atta dal rice]), []string, in order. In focus: []string, in order.map[string]intrice, atta and dalmaps.Keys(stock)nothing yielded yetSorted collects[rice atta dal], saySorted sorts[atta dal rice][]string, in order[atta dal rice]

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. A map has no order. stock holds three keys. Go does not specify the order in which a loop over a map visits them, and the order may change from one walk to the next.
  2. Keys is only a function. maps.Keys(stock) does no work yet. It returns an iterator that, when called, yields the keys in whatever order the map gives them.
  3. Sorted pulls every key. slices.Sorted calls the iterator and appends each key it yields to a new slice, perhaps rice, then atta, then dal.
  4. Then it sorts. With every key collected, Sorted sorts the slice in increasing order, so the order the map gave no longer matters.
  5. The same result every time. The expression returns [atta dal rice] on every run, and a loop over that slice prints the stock in alphabetical order, as stock.go did.

stock.go counts the orders that 1,000 walks over maps.Keys produce, then sorts, then merges a delivery:

stock.go: unordered keys, sorted keys and maps.Insert Go · stock.go
// stock.go: maps.Keys has no order until slices.Sorted gives it one; maps.Insert overwrites.
package main

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

func main() {
	stock := map[string]int{"rice": 40, "atta": 25, "dal": 12}

	// Collect the keys 1,000 times, in whatever order maps.Keys gives them, and count the different orders.
	seen := map[string]bool{}
	for range 1000 {
		seen[fmt.Sprint(slices.Collect(maps.Keys(stock)))] = true
	}
	fmt.Println("more than one order in 1,000 walks:", len(seen) > 1)

	keys := slices.Sorted(maps.Keys(stock))
	fmt.Println("sorted keys:", keys)
	for _, k := range keys {
		fmt.Printf("%s=%d ", k, stock[k])
	}
	fmt.Println()

	// A delivery of 10 kg of rice and 5 kg of salt, merged with maps.Insert.
	delivery := map[string]int{"rice": 10, "salt": 5}
	maps.Insert(stock, maps.All(delivery))
	fmt.Println("after Insert: rice =", stock["rice"], "salt =", stock["salt"])
}

Output

more than one order in 1,000 walks: true
sorted keys: [atta dal rice]
atta=25 dal=12 rice=40 
after Insert: rice = 10 salt = 5

Recorded with Go 1.27.1 on macOS 26 arm64. To run it yourself: go run stock.go

The walks produced more than one order, and Sorted gave [atta dal rice] every time. The last line is a trap: maps.Insert writes every pair of the delivery into the map, so rice went from 40 to 10 instead of 50.

Merging quantities with maps.Insert

maps.Insert(stock, maps.All(delivery)) overwrites each value with the delivery’s. To add quantities, loop: for k, n := range delivery { stock[k] += n }.

Checkpoint

Check: Map order, Sorted and Insert

  1. Question 1 of 2 Why does for k := range maps.Keys(stock) print the keys of stock in different orders from run to run?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The order of Keys is not specified and may change from one call to the next

    Go leaves the order of map walks unspecified. Sort the keys, with slices.Sorted(maps.Keys(m)), whenever the order is visible.

  2. Question 2 of 2 stock has "dal" = 12. A delivery map has "dal" = 8 and "salt" = 5. After maps.Insert(stock, maps.All(delivery)), what is stock["dal"]?

    Type a number.

    A hint

    Insert writes each pair of the sequence into the map.

    Show the answer: Question 2 of 2

    Answer: 8 kg

    Insert overwrites an existing key's value, so dal becomes 8, not 20, just as rice went from 40 to 10 in stock.go. To add quantities, write the loop: for k, n := range delivery { stock[k] += n }.

strings and bytes split text one piece at a time

strings.SplitSeq(s, sep) yields the same pieces as strings.Split, without building the slice, and FieldsSeq does the same for Fields. Lines yields each line with its newline, a last line without one as it is, and nothing at all for an empty string. Package bytes has the same functions for byte slices, and its documentation and that of strings call SplitSeq and Lines single-use iterator: An iterator that can walk its sequence only once, usually because it reads a stream such as an io.Reader. A second walk yields nothing or carries on where the first stopped, and its doc comment should say so. More in “Range-over-func iterators with iter.Seq”:

split.go: SplitSeq, Lines and a loop that stops early Go · split.go
// split.go: strings.SplitSeq, Lines and FieldsSeq yield pieces of a string one at a time.
package main

import (
	"fmt"
	"strings"
)

func main() {
	for col := range strings.SplitSeq("pnr,coach,fare", ",") {
		fmt.Printf("[%s]", col)
	}
	fmt.Println()

	log := "GET /orders 200\nPOST /orders 201\nGET /health 200"
	for line := range strings.Lines(log) {
		fmt.Printf("%q\n", line)
	}

	// Stop at the coach: the loop asks for 2 of the row's 6 fields, and the rest are never cut out.
	row := "4213567890,S4,Asha,34,545,confirmed"
	asked := 0
	for field := range strings.SplitSeq(row, ",") {
		asked++
		if asked == 2 {
			fmt.Println("coach:", field, "after", asked, "fields")
			break
		}
	}
}

Output

[pnr][coach][fare]
"GET /orders 200\n"
"POST /orders 201\n"
"GET /health 200"
coach: S4 after 2 fields

Recorded with Go 1.27.1 on macOS 26 arm64. To run it yourself: go run split.go

The header split into its three columns. Lines kept each newline, so %q shows \n on two of the three lines. The last loop wanted the coach, the second field, and broke there, so SplitSeq never cut out the other 4: splitting this way is lazy evaluation: Producing each value only when the consumer asks for it, so the work stops when the consumer stops. strings.SplitSeq is lazy: a loop that breaks after two pieces leaves the rest of the string unsplit. strings.Split is not..

Comparing lines from Lines

Trim the newline before you compare a line, with strings.TrimSuffix(line, "\n"), or the match fails on every line but the last.

Checkpoint

Check: SplitSeq, FieldsSeq and Lines

  1. Question 1 of 2 What does for l := range strings.Lines("ok\nfail") yield?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: "ok\n", then "fail"

    strings.Lines yields newline-terminated lines with their newline; a final line without one is yielded without one. split.go's three log lines showed both.

  2. Question 2 of 2 A loop over strings.SplitSeq(row, ",") breaks after the second field of a 6-field row. What happens to the other 4 fields?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: They are never cut out, because SplitSeq stops when the loop does

    An iterator does the work for the next value only when the loop asks for it. A break ends the splitting there.

What an iterator saves over a slice, measured in allocations and bytes Pro: opens with a Pro, Premium or Ultimate pass

An iterator saves memory only when nobody collects what it yields. This part counts the allocations and bytes of Split, Fields and Lines against their iterator forms, and of collecting a map’s keys in two ways, then turns the numbers into a rule for when to iterate and when to collect.

In this part:

  • Split, Fields and Lines against their iterator forms, measured
  • Collecting keys: Sorted against a slice sized once, measured at 4 and 10,000 keys
  • Iterate or collect: a rule from the numbers

2 runnable examples (Go) · about 8 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Chunk aliasing, code points, the move from x/exp/maps and reflect's iterators Pro: opens with a Pro, Premium or Ultimate pass

Four details catch people who move to the standard library’s iterators. This part reproduces each one in a program that runs: a write through a chunk, an empty separator on Hindi text, an old build that stops compiling, and a struct walked with reflect.

In this part:

  • Writes and appends through a chunk, and what each does to the original
  • SplitSeq with an empty separator, piece by piece on Hindi text
  • Moving code from golang.org/x/exp/maps to package maps, call by call
  • Walking struct fields, methods and parameters with reflect’s iterators

4 runnable examples (Go) · about 7 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Practise: three helpers rewritten with iterators

practice/helpers.go has three helpers to write with this lesson’s functions, and practice/helpers_test.go tests them on stock, a booking row and fares:

practice/helpers.go and practice/helpers_test.go

Go (helpers.go) · practice/helpers.go

// Package practice holds three helpers to rewrite with the standard library's iterators.
package practice

// KeysInOrder returns the keys of m in increasing order. Use maps.Keys and slices.Sorted.
func KeysInOrder(m map[string]int) []string {
	return nil // write it
}

// ThirdField returns the third comma-separated field of row, or "" when row has fewer than three. Use
// strings.SplitSeq and stop as soon as you have the field.
func ThirdField(row string) string {
	return "" // write it
}

// BatchTotals adds up the values of xs in batches of n, from the left; the last batch may be shorter. Use
// slices.Chunk. It returns nil for an empty xs.
func BatchTotals(xs []int, n int) []int {
	return nil // write it
}

Go (helpers_test.go) · practice/helpers_test.go

package practice

import (
	"fmt"
	"testing"
)

func TestKeysInOrder(t *testing.T) {
	got := KeysInOrder(map[string]int{"rice": 40, "atta": 25, "dal": 12})
	if fmt.Sprint(got) != "[atta dal rice]" {
		t.Errorf("KeysInOrder = %v, want [atta dal rice]", got)
	}
}

func TestThirdField(t *testing.T) {
	for _, c := range []struct{ row, want string }{
		{"4213567890,S4,Asha,34", "Asha"},
		{"4213567890,S4", ""},
		{",,", ""},
	} {
		if got := ThirdField(c.row); got != c.want {
			t.Errorf("ThirdField(%q) = %q, want %q", c.row, got, c.want)
		}
	}
}

func TestBatchTotals(t *testing.T) {
	got := BatchTotals([]int{545, 1450, 2050, 3420, 980}, 2)
	if fmt.Sprint(got) != "[1995 5470 980]" {
		t.Errorf("BatchTotals(…, 2) = %v, want [1995 5470 980]", got)
	}
	if got := BatchTotals(nil, 2); got != nil {
		t.Errorf("BatchTotals(nil, 2) = %v, want nil", got)
	}
}

The starter code compiles, and every test fails:

The starter helpers against their tests Python · practice_session.py
from session import run

# The starter code of practice/ compiles, and every test fails until the three helpers are written.
run(
    """
cd practice && go test .
""",
    expect_failure=["cd practice && go test ."],
)

Output

$ cd practice && go test .
--- FAIL: TestKeysInOrder (0.00s)
    helpers_test.go:11: KeysInOrder = [], want [atta dal rice]
--- FAIL: TestThirdField (0.00s)
    helpers_test.go:22: ThirdField("4213567890,S4,Asha,34") = "", want "Asha"
--- FAIL: TestBatchTotals (0.00s)
    helpers_test.go:30: BatchTotals(…, 2) = [], want [1995 5470 980]
FAIL
FAIL	example.com/practice	1.371s
FAIL
[exit status 1]

This output changes from run to run: go test prints how long each test and the whole run took, which differs from one run to the next.

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 practice_session.py

Make go test . pass in the practice folder. A first hint: BatchTotals ranges over slices.Chunk(xs, n), adds up each chunk and appends the total to its result.

The questions below cover the first three sections, and each answer is explained as soon as you give it.

Practice that adapts to you

Practise this lesson

9 questions about 7 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 9 Which function turns an iter.Seq[E] into a new []E?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: slices.Collect

    slices.Collect, AppendSeq and Sorted take a sequence; slices.All, Values, Backward and Chunk give one.

  2. Question 2 of 9 You only need the elements of a slice, in order, for a function that takes an iter.Seq[string]. Which do you pass?

    Choose one answer.

    Show the answer: Question 2 of 9

    Answer: slices.Values(names)

    slices.Values gives the elements as an iter.Seq; slices.All gives index and element as an iter.Seq2.

  3. Question 3 of 9 What does slices.Chunk([]int{545, 1450, 2050, 3420, 980}, 2) yield?

    Choose one answer.

    Show the answer: Question 3 of 9

    Answer: [545 1450], [2050 3420], [980]

    Chunk cuts a slice into consecutive pieces of n, and the last piece may be shorter.

  4. Question 4 of 9 What does slices.Sorted(maps.Keys(map[string]int{"rice": 40, "atta": 25, "dal": 12})) return?

    Choose one answer.

    Show the answer: Question 4 of 9

    Answer: [atta dal rice]

    Whatever order maps.Keys gives, slices.Sorted returns the keys in increasing order.

  5. Question 5 of 9 This loop must print the stock in the same order on every run. Which version does it?

    Read the code, then choose one answer.

    stock := map[string]int{"rice": 40, "atta": 25, "dal": 12}
    // print "name=quantity " for each item
    Show the answer: Question 5 of 9

    Answer: for _, k := range slices.Sorted(maps.Keys(stock)) { fmt.Printf("%s=%d ", k, stock[k]) }

    Only sorting fixes the order. Range over slices.Sorted(maps.Keys(stock)) and read stock[k] for each key.

  6. Question 6 of 9 A shop merges a supplier's delivery into its stock with maps.Insert, and its stock reports keep showing less rice than it bought. Why?

    Choose one answer.

    Show the answer: Question 6 of 9

    Answer: Insert overwrites each key's value with the delivery's instead of adding to it

    maps.Insert is a copy, not an addition. Write the loop when values must be combined.

  7. Question 7 of 9 What does split.go print?

    What does this program print? Choose one answer.

    // split.go: strings.SplitSeq, Lines and FieldsSeq yield pieces of a string one at a time.
    package main
    
    import (
    	"fmt"
    	"strings"
    )
    
    func main() {
    	for col := range strings.SplitSeq("pnr,coach,fare", ",") {
    		fmt.Printf("[%s]", col)
    	}
    	fmt.Println()
    
    	log := "GET /orders 200\nPOST /orders 201\nGET /health 200"
    	for line := range strings.Lines(log) {
    		fmt.Printf("%q\n", line)
    	}
    
    	// Stop at the coach: the loop asks for 2 of the row's 6 fields, and the rest are never cut out.
    	row := "4213567890,S4,Asha,34,545,confirmed"
    	asked := 0
    	for field := range strings.SplitSeq(row, ",") {
    		asked++
    		if asked == 2 {
    			fmt.Println("coach:", field, "after", asked, "fields")
    			break
    		}
    	}
    }
    Show the answer: Question 7 of 9

    Answer: it prints

    [pnr][coach][fare]
    "GET /orders 200\n"
    "POST /orders 201\n"
    "GET /health 200"
    coach: S4 after 2 fields

    SplitSeq yields what Split would return, one piece at a time; Lines keeps each newline; a break stops the splitting.

  8. Question 8 of 9 What does strings.FieldsSeq(" GET /orders ") yield?

    Choose one answer.

    Show the answer: Question 8 of 9

    Answer: "GET", then "/orders"

    FieldsSeq yields the words between runs of white space, as unicode.IsSpace defines it, and never an empty word.

  9. Question 9 of 9 A function counts lines with for range strings.Lines(s) { n++ }. What does it return for s = "" and for s = "a\nb\n"?

    Choose one answer.

    Show the answer: Question 9 of 9

    Answer: 0 and 2

    strings.Lines yields each newline-terminated line and a final unterminated one, if any; an empty string yields nothing. Counting with Split(s, "\n") is off by one for text that ends in a newline.

Interview questions

Warm-up (fresher): What does slices.Sorted(maps.Keys(m)) return, and why use it to print a map’s keys?

It returns a new slice of the map’s keys in increasing order. A map has no order of its own: Go does not specify the order of a loop over a map or of maps.Keys, and it can change between walks. Sorting the collected keys gives the same output on every run.

Three longer questions of the module bank build on this lesson:

  1. Mid to senior

    A log parser calls strings.Split on 50,000 lines a second to read the status code, the third of nine fields, and profiles show heavy allocation and garbage collection. What would you change, what would you measure before and after, and when would you keep Split?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A batch uploader splits orders with slices.Chunk(orders, 100), appends a trailer record to each batch and rounds every amount in a batch before sending. Afterwards some orders in the caller's slice have changed. Which operation changed them, why, and how do you make the uploader safe?

    Model answer, Pro. First step and follow-ups

  3. Mid to staff

    Your team replaces golang.org/x/exp/maps with the standard maps package across a large codebase, and dozens of builds break at lines like keys := maps.Keys(m); sort.Strings(keys). Explain the failure, give the fix for each kind of call site, and say how you would roll the change out.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • The iterators in slices walk a slice forwards, backwards or in chunks; Collect, AppendSeq and Sorted turn a sequence back into a slice.
  • A map has no order, so sorted output takes slices.Sorted over maps.Keys; maps.Insert overwrites values, it never adds them.
  • SplitSeq, FieldsSeq and Lines yield the pieces of a string one at a time, Lines with each newline kept, and a loop that stops early stops the splitting.

Terms in this lesson

lazy evaluation
Producing each value only when the consumer asks for it, so the work stops when the consumer stops. strings.SplitSeq is lazy: a loop that breaks after two pieces leaves the rest of the string unsplit. strings.Split is not.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.