Skip to main content

Introduction

The range keyword iterates over elements in a variety of built-in data structures. It provides a clean, idiomatic way to loop over slices, arrays, maps, and strings, automatically handling the iteration details.

The Code

// _range_ iterates over elements in a variety of
// built-in data structures. Let's see how to
// use `range` with some of the data structures
// we've already learned.

package main

import "fmt"

func main() {

	// Here we use `range` to sum the numbers in a slice.
	// Arrays work like this too.
	nums := []int{2, 3, 4}
	sum := 0
	for _, num := range nums {
		sum += num
	}
	fmt.Println("sum:", sum)

	// `range` on arrays and slices provides both the
	// index and value for each entry. Above we didn't
	// need the index, so we ignored it with the
	// blank identifier `_`. Sometimes we actually want
	// the indexes though.
	for i, num := range nums {
		if num == 3 {
			fmt.Println("index:", i)
		}
	}

	// `range` on map iterates over key/value pairs.
	kvs := map[string]string{"a": "apple", "b": "banana"}
	for k, v := range kvs {
		fmt.Printf("%s -> %s\n", k, v)
	}

	// `range` can also iterate over just the keys of a map.
	for k := range kvs {
		fmt.Println("key:", k)
	}

	// `range` on strings iterates over Unicode code
	// points. The first value is the starting byte index
	// of the `rune` and the second the `rune` itself.
	// See [Strings and Runes](strings-and-runes) for more
	// details.
	for i, c := range "go" {
		fmt.Println(i, c)
	}
}

Range with Different Types

Range Over Slices and Arrays

nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
	sum += num
}
When ranging over slices or arrays, you get two values per iteration:
  1. Index - the position in the slice/array
  2. Value - the element at that position
Use the blank identifier _ to ignore values you don’t need.
The blank identifier _ is used to ignore return values you don’t need. This is idiomatic Go and more explicit than creating an unused variable.

Getting Index and Value

for i, num := range nums {
	if num == 3 {
		fmt.Println("index:", i)
	}
}
When you need both the index and value, capture both variables in the for loop.

Range Over Maps

kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
	fmt.Printf("%s -> %s\n", k, v)
}
Ranging over a map yields key/value pairs. The first variable is the key, the second is the value.
Map iteration order is not guaranteed and may vary between runs. Don’t rely on maps being iterated in any particular order.

Range Over Map Keys Only

for k := range kvs {
	fmt.Println("key:", k)
}
If you only need the keys, use a single variable. The values are not computed or returned.

Range Over Strings

for i, c := range "go" {
	fmt.Println(i, c)
}
// Output:
// 0 103  (g)
// 1 111  (o)
Ranging over a string iterates over Unicode code points (runes), not bytes:
  • First value (i): Starting byte index of the rune
  • Second value (c): The rune itself as an int32
For multi-byte UTF-8 characters, the byte index may not increment by 1 each iteration. For example, ‘世’ takes 3 bytes, so indices might be 0, 3, 6, etc.

Range Patterns

PatternSyntaxUse Case
Value onlyfor _, v := range sliceProcess each element
Index and valuefor i, v := range sliceNeed position info
Index onlyfor i := range sliceJust need positions
Key and valuefor k, v := range mapProcess map entries
Key onlyfor k := range mapJust need keys
Rune iterationfor i, c := range strProcess Unicode characters

Range Return Values Summary

Data StructureFirst ValueSecond Value
Slice/ArrayIndex (int)Element value
MapKeyValue
StringByte index (int)Rune (int32)
ChannelElement value(none)

Common Range Patterns

Filtering a Slice

var evens []int
for _, num := range nums {
    if num%2 == 0 {
        evens = append(evens, num)
    }
}

Finding in a Slice

found := false
for _, item := range items {
    if item == target {
        found = true
        break
    }
}

Transforming a Slice

doubled := make([]int, len(nums))
for i, num := range nums {
    doubled[i] = num * 2
}

Collecting Map Keys

keys := make([]string, 0, len(m))
for k := range m {
    keys = append(keys, k)
}

Important Notes

The value returned by range is a copy of the element, not a reference to it. Modifying the loop variable won’t change the original collection.
// This does NOT modify the slice
for _, v := range nums {
    v = v * 2  // Only modifies the copy
}

// This DOES modify the slice
for i := range nums {
    nums[i] = nums[i] * 2
}

Range vs Traditional For Loop

Use Range When:

  • You want to iterate over all elements
  • You need cleaner, more readable code
  • You’re working with built-in types (slices, maps, strings)

Use Traditional For When:

  • You need more control over iteration (step size, direction)
  • You want to modify the original collection
  • You need to iterate a specific number of times

Key Takeaways

  • range provides a clean way to iterate over collections
  • Returns different values based on the data structure type
  • Use _ to ignore values you don’t need
  • Works with slices, arrays, maps, strings, and channels
  • Map iteration order is not guaranteed
  • String iteration processes Unicode runes, not bytes
  • Range values are copies - use indices to modify originals
  • For slices/arrays: returns index and value
  • For maps: returns key and value
  • For strings: returns byte index and rune

Build docs developers (and LLMs) love