Go For Loops
The for loop is Go's only looping construct, but don't let that fool you - it's incredibly versatile! Unlike many other programming languages that have separate loop constructs like while and do-while, Go simplifies things by providing a single, flexible looping structure that can be adapted to handle all your iteration needs.
Introduction to For Loops
In Go, the for loop serves as the universal tool for repetitive tasks. Whether you need to iterate a specific number of times, loop while a condition is true, or process items in a collection, the for loop has you covered.
If you're coming from other programming languages, you'll find Go's approach refreshingly simple - just one loop construct to learn!
Basic For Loop Syntax
Let's start with the most common form of the for loop, which looks similar to what you might find in C, Java, or JavaScript:
for initialization; condition; post {
    // Loop body
}
Where:
- initialization: Executes once before the loop starts
- condition: Checked before each iteration; the loop continues while this is true
- post: Executes at the end of each iteration
Here's a simple example:
package main
import "fmt"
func main() {
    for i := 0; i < 5; i++ {
        fmt.Println("Iteration:", i)
    }
}
Output:
Iteration: 0
Iteration: 1
Iteration: 2
Iteration: 3
Iteration: 4
How It Works:
- i := 0- We initialize a variable- ito 0 (this runs once)
- i < 5- Before each iteration, we check if- iis less than 5
- If the condition is true, we execute the loop body
- i++- After each iteration, we increment- i
- Repeat steps 2-4 until the condition becomes false
For as a While Loop
Go doesn't have a dedicated while loop. Instead, you can use the for loop with just a condition:
package main
import "fmt"
func main() {
    sum := 1
    for sum < 100 {
        sum += sum
        fmt.Println("Current sum:", sum)
    }
    fmt.Println("Final sum:", sum)
}
Output:
Current sum: 2
Current sum: 4
Current sum: 8
Current sum: 16
Current sum: 32
Current sum: 64
Current sum: 128
Final sum: 128
In this example, we're doubling sum repeatedly until it exceeds 100. The loop behaves like a while loop in other languages.
Infinite Loops
You can create an infinite loop by omitting the condition entirely:
for {
    // This will run forever unless broken
    if someCondition {
        break  // Exit the loop
    }
}
Here's a practical example:
package main
import (
    "fmt"
    "math/rand"
    "time"
)
func main() {
    // Seed the random number generator
    rand.Seed(time.Now().UnixNano())
    
    counter := 0
    
    for {
        // Generate random number between 0 and 9
        num := rand.Intn(10)
        counter++
        
        fmt.Printf("Attempt %d: Got number %d
", counter, num)
        
        // Exit when we get a 7
        if num == 7 {
            fmt.Printf("Found 7 after %d attempts!
", counter)
            break
        }
        
        // Safety valve - don't run more than 20 iterations
        if counter >= 20 {
            fmt.Println("Giving up after 20 attempts")
            break
        }
    }
}
This will keep generating random numbers until it either finds a 7 or makes 20 attempts.
Always ensure infinite loops have a way to terminate, such as a break statement, or they'll run forever and potentially crash your program!
For-Range Loop
One of the most powerful variations of the for loop in Go is the "for-range" loop, which is used to iterate over elements in various data structures:
package main
import "fmt"
func main() {
    // Iterating over a slice
    fruits := []string{"apple", "banana", "cherry", "dragonfruit"}
    
    fmt.Println("Fruits in my basket:")
    for index, fruit := range fruits {
        fmt.Printf("%d: %s
", index, fruit)
    }
    
    // Iterating over a map
    calories := map[string]int{
        "apple":       52,
        "banana":      96,
        "cherry":      50,
        "dragonfruit": 60,
    }
    
    fmt.Println("
Calorie content:")
    for fruit, cal := range calories {
        fmt.Printf("%s: %d calories
", fruit, cal)
    }
}
Output:
Fruits in my basket:
0: apple
1: banana
2: cherry
3: dragonfruit
Calorie content:
apple: 52 calories
banana: 96 calories
cherry: 50 calories
dragonfruit: 60 calories
The range form of the for loop iterates over:
- Arrays or slices
- Strings (by rune)
- Maps
- Channels
What You Get With Range
The values returned by range depend on the type being iterated:
| Type | First Value | Second Value | 
|---|---|---|
| Array/Slice | Index | Value at index | 
| String | Index | Unicode code point (rune) | 
| Map | Key | Value | 
| Channel | Value | - | 
Using the Blank Identifier
If you only need one of the values from range, you can use the blank identifier (_) to ignore the other:
// If you only need the values, not the indices
for _, fruit := range fruits {
    fmt.Println("I have a", fruit)
}
// If you only need the indices
for i, _ := range fruits {
    fmt.Println("Fruit index:", i)
}
// OR more concisely:
for i := range fruits {
    fmt.Println("Fruit index:", i)
}
Control Flow in Loops
Go provides two important statements for controlling the flow within loops:
Break
The break statement exits the innermost loop immediately:
package main
import "fmt"
func main() {
    fmt.Println("Finding the first number divisible by both 3 and 7:")
    
    for i := 1; i <= 100; i++ {
        if i%3 == 0 && i%7 == 0 {
            fmt.Printf("Found it! %d is divisible by both 3 and 7
", i)
            break
        }
        fmt.Printf("Checked %d
", i)
    }
}
This will stop as soon as it finds the first number (21) that's divisible by both 3 and 7.
Continue
The continue statement skips the rest of the current iteration and jumps to the next iteration:
package main
import "fmt"
func main() {
    fmt.Println("Printing only odd numbers:")
    
    for i := 1; i <= 10; i++ {
        if i%2 == 0 {
            // Skip even numbers
            continue
        }
        fmt.Println(i)
    }
}
Output:
Printing only odd numbers:
1
3
5
7
9
Labeled Loops
Go allows you to label loops and use those labels with break and continue statements to control outer loops:
package main
import "fmt"
func main() {
    fmt.Println("Multiplication table:")
    
outer:
    for i := 1; i <= 5; i++ {
        for j := 1; j <= 5; j++ {
            if i*j > 15 {
                fmt.Println("Stopping at", i, "*", j, "=", i*j)
                break outer // Break out of both loops
            }
            fmt.Printf("%d * %d = %d
", i, j, i*j)
        }
    }
}
This example will print the multiplication table but stop completely once the product exceeds 15.
Practical Examples
Example 1: File Processing Line by Line
Here's how you might use a loop to process a file line by line:
package main
import (
    "bufio"
    "fmt"
    "os"
    "strings"
)
func main() {
    // Sample text file content
    fileContent := `Hello, World!
This is line 2.
And this is line 3.
#This is a comment
The end.`
    // Create a string reader (in a real program, this would be a file)
    reader := strings.NewReader(fileContent)
    scanner := bufio.NewScanner(reader)
    
    lineNumber := 0
    
    // Scan line by line
    for scanner.Scan() {
        lineNumber++
        line := scanner.Text()
        
        // Skip comment lines
        if strings.HasPrefix(line, "#") {
            fmt.Printf("Line %d: (skipping comment)
", lineNumber)
            continue
        }
        
        fmt.Printf("Line %d: %s
", lineNumber, line)
    }
    
    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, "reading input:", err)
    }
}
Output:
Line 1: Hello, World!
Line 2: This is line 2.
Line 3: And this is line 3.
Line 4: (skipping comment)
Line 5: The end.
Example 2: Implementing a Simple Retry Mechanism
package main
import (
    "fmt"
    "math/rand"
    "time"
)
func main() {
    rand.Seed(time.Now().UnixNano())
    
    const maxRetries = 5
    success := false
    
    for attempt := 1; attempt <= maxRetries; attempt++ {
        fmt.Printf("Attempt %d of %d...
", attempt, maxRetries)
        
        // Simulate an operation that might fail
        if rand.Float64() < 0.3 { // 30% chance of success
            fmt.Println("Success!")
            success = true
            break
        }
        
        fmt.Println("Failed. Retrying...")
        
        // Add exponential backoff between retries
        if attempt < maxRetries {
            backoff := time.Duration(attempt * attempt * 100) * time.Millisecond
            fmt.Printf("Waiting for %v before next attempt
", backoff)
            time.Sleep(backoff)
        }
    }
    
    if !success {
        fmt.Println("Operation failed after maximum number of retries")
    }
}
This example shows how to implement a retry mechanism with exponential backoff, which is a common pattern in network operations.
Visual Representation
Best Practices
- Keep loops simple - If your loop body is getting too complex, consider extracting some logic into separate functions.
- Avoid modifying loop variables inside the loop body (except in the post statement).
- Be cautious with infinite loops - Always ensure there's a way to exit.
- Prefer range-based loops when iterating over collections.
- Use appropriate variable scoping - Variables declared in the init statement are scoped to the loop.
Common Gotchas
Variable Capturing in Closures
One common issue occurs when using variables from loops in closures:
package main
import "fmt"
func main() {
    functions := make([]func(), 3)
    
    // This will not work as expected!
    for i := 0; i < 3; i++ {
        functions[i] = func() {
            fmt.Println(i)  // Captures the variable i, not its value
        }
    }
    
    // All functions will print the same value (3)
    for _, f := range functions {
        f()
    }
    
    fmt.Println("Fixed version:")
    
    // The correct way: create a new variable in each iteration
    functions = make([]func(), 3)
    for i := 0; i < 3; i++ {
        val := i  // Create a new variable
        functions[i] = func() {
            fmt.Println(val)
        }
    }
    
    for _, f := range functions {
        f()
    }
}
The fixed version will print 0, 1, 2 as expected.
Summary
In this tutorial, we've explored Go's versatile for loop in its various forms:
- Basic three-component loops (like C-style for loops)
- Condition-only loops (like while loops)
- Infinite loops with break conditions
- Range-based loops for collections
We've also covered important control flow statements like break and continue, as well as more advanced concepts like labeled loops.
Go's simplified approach to loops actually makes your code more readable and maintainable once you get used to it. By mastering the for loop in its different forms, you'll be well-equipped to handle any iteration needs in your Go programs.
Exercises
- Write a program that prints all even numbers between 1 and 20.
- Create a function that finds the largest number in a slice using a for loop.
- Write a program that counts the frequency of each word in a string.
- Implement a simple "FizzBuzz" program using for loops: Print numbers from 1 to 100, but for multiples of 3 print "Fizz", for multiples of 5 print "Buzz", and for multiples of both print "FizzBuzz".
- Write a nested loop that prints a simple pattern of asterisks in the shape of a right triangle.
Additional Resources
- Go Tour: For loop
- Effective Go: For loops
- Go by Example: For loops
- Go Playground - Try out your loops interactively
💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!