mdawar.dev

A blog about programming, Web development, Open Source, Linux and DevOps.

Go - Loops

Go has only 1 looping construct which is the for loop.

The for statement specifies repeated execution of a block.

For Loop Components

The for loop has 3 components separated by semicolons:

  1. The init statement:

    • Executed once before evaluating the condition for the first iteration
    • May be a short variable declaration
    • Variables declared by the init statement are re-used in each iteration
    • The declared variables are only visible in the scope of the for loop
  2. The condition expression:

    • Evaluated before each iteration
    • The block is executed as long as a boolean condition evaluates to true
    • If absent, it is equivalent to the boolean value true
  3. The post statement:

    • Executed after each execution of the block
    • An increment or decrement statement
go
for init; condition; post {
  // ...
}

For example:

go
for i := 0; i < 10; i++ {
  // Print 0 to 9 each on a new line.
  fmt.Println(i)
}

Single Condition

The for loop can be used with only the condition expression which makes it act like a while loop in other languages.

go
i := 1

// Repeat the execution as long as the condition evaluates to `true`.
for i < 100 {
  i += i
}

Infinite Loop

If the condition is absent, it is equivalent to the boolean value true which creates an infinite loop.

go
// Infinite loop.
for {
  // Do something.

  if someCondition {
    // The break statement can be used to exit the loop
    // when a certain condition is met.
    break
  }
}

// Same as
for true {
  // ...
}

Range Loop

The for range loop can be used to iterate over arrays, slices, strings, maps or channel values.

The range expression is evaluated once before beginning the loop, it generates 2 iteration variables, one for the index and the second for the value, except for channels only 1 variable with the value is generated.

To omit the index, the blank identifier _ may be used in place of the index variable.

Array or pointer to an array:

go
arr := [5]string{"a", "b", "c", "d", "e"}

// The first iteration variable is the index (starting at 0).
// The second iteration variable is the value.
for i, v := range arr {
  fmt.Println(i, v)
}

// Using a pointer to an array.
// Omit the index using the blank identifier.
for _, v := range &arr {
  fmt.Println(i, v)
}

Slice:

go
slice := []string{"a", "b", "c", "d", "e"}

// The first iteration variable is the index (starting at 0).
// The second iteration variable is the value.
for i, v := range slice {
  fmt.Println(i, v)
}

String:

go
str := "Hello, World"

// The first iteration variable is the byte index (starting at 0).
// The second iteration variable is the Unicode code point (rune type).
// If the iteration encounters an invalid UTF-8 sequence
// the second value will be 0xFFFD (Unicode replacement character).
for i, r := range str {
  fmt.Println(i, r, string(r))
}

Map:

go
m := map[string]int{"a": 1, "b": 2, "c": 3}

// The iteration order over maps is not guaranteed.
// The first iteration variable is the key.
// The second iteration variable is the value.
for k, v := range m {
  fmt.Println(k, v)
}

Channel:

go
var ch chan int = producer()

// For channels there's only 1 iteration variable, it will hold
// the successive values sent on the channel until the channel is closed.
for i := range ch {
  fmt.Println(i)
}

// Empty a channel.
for range ch {}

Break Out of a Loop

The break statement can be used to break out of a loop early.

go
for i := 0; i < 10; i++ {
  if i == 5 {
    break
  }

  // Only 0 to 4 will be printed.
  fmt.Println(i)
}

To break out of nested loops, a label can be used:

go
package main

import "fmt"

func main() {
  arr := [][]string{
    {"a", "b", "c", "d", "e"},
    {"f", "g", "h", "i", "j"},
    {"k", "l", "m", "n", "o"},
    {"p", "q", "r", "s", "t"},
    {"u", "v", "w", "x", "y"},
    {"z"},
  }

  letterToFind := "m"

outer:
  for i := 0; i < len(arr); i++ {
    // Loop through the nested slice elements.
    for j := 0; j < len(arr[i]); j++ {
      // Check if the current letter is the one we're searching for.
      if arr[i][j] == letterToFind {
        fmt.Printf("The letter was found at arr[%d][%d]\n", i, j)

        break outer
      }
    }
  }
}

Skip The Current Iteration

The continue statement skips the current iteration of the loop and moves to the next.

go
for i := 0; i < 10; i++ {
  if i%2 == 0 {
    // Skip even numbers (move on to the next iteration).
    continue
  }

  // Only 1, 3, 5, 7, 9 are printed.
  fmt.Println(i)
}

The continue statement can also be used with a label:

go
RowLoop:
for y, row := range rows {
  // Inner loop.
  for x, data := range row {
    if data == endOfRow {
      // Continue the execution of the outer loop.
      continue RowLoop
    }
  }

  // Do something.
}

Iteration Variable Capture

Anonymous functions created inside for loops that reference the iteration variable and have their execution delayed will capture and share the same variable.

By the time these functions are called, the for loop has been completed and the variable has been updated several times and will hold the value from the final iteration.

go
package main

import "fmt"

func main() {
  for i := 0; i < 10; i++ {
    // Delay the execution of the anonymous functions.
    defer func() {
      fmt.Println(i) // All the functions will print the last value `10`
    }()
  }
}

Workarounds

Create a new variable inside the loop:

go
package main

import "fmt"

func main() {
  for i := 0; i < 10; i++ {
    // Create a new variable that holds the iteration variable's value.
    // If the same variable name is used, it will shadow the outer variable.
    i := i // Capture the current value in a new variable

    defer func() {
      fmt.Println(i) // Each function will print the correct value
    }()
  }
}

Pass the iteration variable as an argument to the closure:

go
package main

import "fmt"

func main() {
  for i := 0; i < 10; i++ {
    // The function parameter can have the same variable name
    // which shadows the iteration variable.
    defer func(i int) {
      fmt.Println(i) // Each function will print the correct value
    }(i) // Pass the variable as an argument
  }
}