skip to content

Collecting and Emitting Sequences

slices.Collect, slices.Sorted, maps.Keys and maps.Values are the adapters between a sequence and a real container. Interviewers check you know which direction each of them runs.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

questions

4

In Go's `maps` package, what does `maps.Keys(m)` return, and how do you turn it into a slice?

level: juniorimportance: must knowfreq 52%

answer

  1. it is not a []K
  2. nothing has been computed yet
  3. a function value you range over
  4. iter.Seq[K], added in Go 1.23
  5. slices.Collect drains it into a slice

basics

~10 s

maps.Keys returns an iterator of type iter.Seq[K], not a slice. You can range over it directly, or convert it with slices.Collect(maps.Keys(m)) to get a []K. slices.Sorted(maps.Keys(m)) collects and sorts in one call.

solid answer

~40 s

Since Go 1.23, `maps.Keys(m)` returns an `iter.Seq[K]` — a function value you range over, not a `[]K`. That is the single biggest gotcha, because the older `golang.org/x/exp/maps.Keys` returned a slice, so code and muscle memory ported from it will not compile: you cannot index it, call `len` on it, or append to it. To get a real slice you pass it to a collector: `slices.Collect(maps.Keys(m))` builds a `[]K` in unspecified order, and `slices.Sorted(maps.Keys(m))` builds one in ascending order for any `cmp.Ordered` key type. If you only want to loop once, skip the helpers entirely and write `for k := range m` — that allocates nothing. The same shape holds for `maps.Values` (an `iter.Seq[V]`) and `maps.All` (an `iter.Seq2[K, V]`).

code

go · 11 lines
go
m := map[string]int{"a": 1, "b": 2}

// Just looping? No helper needed — and no allocation.
for k := range m {
	_ = k
}

// Need an actual []string to keep, index or return:
keys := slices.Collect(maps.Keys(m)) // order is unspecified
names := slices.Sorted(maps.Keys(m)) // collected, then sorted
_, _ = keys, names

go deeper

for a junior

Recall that maps.Keys hands back an iterator, not a slice, and that slices.Collect is how you get a real []K. Also be ready to say that a plain for k := range m needs no helper at all.

for a middle

Explain that iter.Seq[K] is a function type, so nothing is computed until something ranges it, and say which collector you would use: Collect, Sorted, SortedFunc or AppendSeq, and what each costs.

for a senior

Show judgment about when materialising keys is worth an allocation at all, and spot the review smell of collecting a slice purely to loop over it once in a hot path.

for a principal

Frame the wider tradeoff: returning sequences instead of slices from your own packages pushes the allocation decision to the caller, at the price of an API that is harder to use and pins consumers to Go 1.23 or later.

## What `maps.Keys` actually gives you Go 1.23 added range-over-function and the `iter` package, and with it a set of standard-library helpers that speak in *sequences* rather than slices. `maps.Keys` is one of them: ```go func Keys[Map ~map[K]V, K comparable, V any](m Map) iter.Seq[K] ``` `iter.Seq[K]` is just a named function type: ```go type Seq[V any] func(yield func(V) bool) ``` So the value you get back is a **function**, not data. Nothing has been computed when `maps.Keys(m)` returns. The keys are produced only when something drives that function — either a `for ... range` loop over it, or a helper that consumes it. That is why every slice operation you might reach for fails to compile: ```go keys := maps.Keys(m) n := len(keys) // does not compile: len is not defined on a func type k := keys[0] // does not compile: cannot index a func value ``` ## Why this trips people up Before the standard library had iterators, the community used `golang.org/x/exp/maps`, whose `Keys` returned `[]K` directly. When those helpers were promoted into the standard `maps` package, the signature deliberately changed to an iterator. So the same call expression exists in both worlds with different result types — one of the few places in Go where copying a familiar line into a new file silently changes meaning. If you see `len(maps.Keys(m))` in a code review, the author is thinking of the `x/exp` version. ## Turning a sequence into a slice The `slices` package holds the collectors: - `slices.Collect(seq iter.Seq[E]) []E` — drains the sequence and appends every element into a new slice. `slices.Collect(maps.Keys(m))` is the direct replacement for the old slice-returning helper. The resulting order is whatever the map iteration produced, which is not specified. - `slices.Sorted(seq iter.Seq[E]) []E` — collects and then sorts, for element types satisfying `cmp.Ordered` (integers, floats, strings). `slices.Sorted(maps.Keys(m))` is one call instead of collect-then-`slices.Sort`. - `slices.SortedFunc(seq, cmp)` — the same, with your own comparison function returning a negative, zero or positive `int`, for key types that are not ordered by `<`. - `slices.AppendSeq(dst, seq) []E` — appends into a slice you already have, so you can reuse capacity instead of allocating a fresh backing array each time. Collecting an empty sequence gives you a slice of length zero; do not rely on it being non-nil, and do not rely on `slices.Collect` reserving capacity in advance — it grows the slice as it drains, the same way `append` does. ## The companions `maps.Values(m)` returns `iter.Seq[V]` — the values, again as a sequence. `maps.All(m)` returns `iter.Seq2[K, V]`, a two-value sequence, which is what you want when you need pairs. `iter.Seq2` is why `for k, v := range maps.All(m)` compiles while `for k, v := range maps.Keys(m)` does not: a one-value sequence can only be ranged with one iteration variable. In the other direction, `maps.Collect(seq iter.Seq2[K, V]) map[K]V` builds a map out of a two-value sequence, and `maps.Insert(m, seq)` writes the pairs into an existing map. ## When you need none of this The helpers exist to *adapt* — to hand a map's contents to something that consumes an `iter.Seq`, or to materialise a slice you genuinely need to keep, index, sort or return. If all you are doing is looping once over the keys, `for k := range m` is shorter, allocates nothing and does not depend on Go 1.23. Wrapping it as `for k := range maps.Keys(m)` adds a function call per element and buys nothing; collecting into a slice first adds an allocation on top of that. The rule of thumb: range the map directly to read it, and use `maps.Keys` plus a collector only when the slice itself is the thing you need, or when the consumer's parameter type is `iter.Seq[K]`.

  • Why does the standard library's maps.Keys return an iterator when golang.org/x/exp/maps.Keys returned a slice?
    An iterator lets the caller decide whether to allocate. Ranging it costs nothing extra, feeding it to `slices.Sorted` sorts without a second pass over a temporary, and a consumer that only needs the first match can stop early. The slice version forced an allocation of every key on every caller, even ones that discarded most of them.
  • What is the difference between slices.Collect and slices.Sorted when applied to maps.Keys(m)?
    `slices.Collect` drains the sequence into a new slice and stops there, so the order is whatever came out. `slices.Sorted` does the same collection and then sorts the result in ascending order, and it constrains the element type to `cmp.Ordered`. For a key type that is not ordered by `<` — a struct, say — use `slices.SortedFunc` with your own comparison.
  • How do you get key and value pairs rather than just keys?
    Use `maps.All(m)`, which returns `iter.Seq2[K, V]` and can be ranged as `for k, v := range maps.All(m)`. `maps.Keys` returns a one-value `iter.Seq[K]`, so ranging it with two variables is a compile error. For values alone there is `maps.Values(m)`, an `iter.Seq[V]`.

saying these in an interview costs you the question

  • Says maps.Keys returns a []K you can index
  • Calls len() on the value maps.Keys returns
  • Assumes the keys come back in insertion order
  • Thinks maps.Keys precomputes the keys eagerly
  • Collects into a slice just to loop over it once
open as a page

What do Go's `slices.Values` and `slices.All` return, and when would you need them?

level: middleimportance: should knowfreq 38%

basics

~20 s

slices.Values(s) returns an iter.Seq of the elements; slices.All(s) returns an iter.Seq2 of index and element pairs. Both adapt a slice into a sequence value so it can be passed to code whose parameter type is an iterator.

open as a page

`slices.Collect` over an unbounded sequence OOMs a service — how do you confirm that and fix it?

level: seniorimportance: should knowfreq 34%

basics

~20 s

slices.Collect drains the whole sequence into one slice, so memory grows with the number of elements yielded and the iterator's laziness is discarded. Confirm with a heap profile: inuse_space dominated by the growing backing array under that call. Fix by streaming or capping instead of collecting.

open as a page

In Go's `maps` package, when do you use `maps.Insert` instead of `maps.Collect`?

level: middleimportance: nice to knowfreq 26%

basics

~20 s

maps.Collect allocates and returns a brand new map from a two-value sequence. maps.Insert writes the pairs into a map you already have and returns nothing. Use Insert to merge several sequences into one map, or to fill a map you pre-sized.

open as a page