
How to Construct For Loops in Go
Go’s for loop is the only looping construct in the language, but don’t let that fool you into thinking it’s limiting. This single construct is incredibly versatile and can handle everything from basic iteration to complex control flow patterns. Understanding for loops is essential for Go developers because they’re used everywhere – from processing slices and maps to implementing servers and handling concurrent operations. In this post, you’ll learn the various forms of for loops in Go, when to use each one, common gotchas that trip up developers, and performance considerations that can make or break your applications.
How Go For Loops Work
Unlike many languages that have multiple loop types (while, do-while, foreach), Go simplifies things with a single for statement that can take several forms. The basic syntax follows this pattern:
for initialization; condition; post {
// loop body
}
But Go’s for loop is more flexible than this basic form suggests. Here are the main variations:
- Classic three-part loop: Similar to C-style for loops
- Condition-only loop: Acts like a while loop
- Infinite loop: Runs until explicitly broken
- Range loop: Iterates over collections, channels, or strings
The compiler handles these variations efficiently, often optimizing simple loops into more performant assembly code. Range loops, in particular, receive special treatment and can be significantly faster than manual indexing for certain operations.
Step-by-Step Implementation Guide
Basic Three-Part For Loop
This is your bread-and-butter loop for numeric iteration:
package main
import "fmt"
func main() {
// Basic counting loop
for i := 0; i < 10; i++ {
fmt.Printf("Iteration %d\n", i)
}
// You can modify any part
for i := 10; i > 0; i -= 2 {
fmt.Printf("Countdown: %d\n", i)
}
// Multiple variables (less common but useful)
for i, j := 0, 100; i < j; i, j = i+1, j-1 {
fmt.Printf("i=%d, j=%d\n", i, j)
}
}
Condition-Only Loops
When you need while-loop behavior:
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
// Acts like a while loop
attempts := 0
for attempts < 5 {
if rand.Intn(10) > 7 {
fmt.Printf("Success on attempt %d!\n", attempts+1)
break
}
attempts++
fmt.Printf("Attempt %d failed, trying again...\n", attempts)
}
}
Infinite Loops with Break Conditions
Perfect for servers, event loops, or when you need complex exit conditions:
package main
import (
"fmt"
"time"
)
func main() {
counter := 0
for {
counter++
fmt.Printf("Running iteration %d\n", counter)
// Multiple exit conditions
if counter > 10 {
fmt.Println("Max iterations reached")
break
}
if counter%3 == 0 {
fmt.Println("Skipping some work...")
continue
}
// Simulate some work
time.Sleep(100 * time.Millisecond)
}
}
Range Loops - The Go Way
Range loops are idiomatic Go and handle most collection iteration needs:
package main
import "fmt"
func main() {
// Slice iteration
fruits := []string{"apple", "banana", "cherry", "date"}
// Both index and value
for i, fruit := range fruits {
fmt.Printf("Index %d: %s\n", i, fruit)
}
// Value only (index ignored with _)
for _, fruit := range fruits {
fmt.Printf("Fruit: %s\n", fruit)
}
// Index only
for i := range fruits {
fmt.Printf("Index: %d\n", i)
}
// Map iteration
scores := map[string]int{
"Alice": 95,
"Bob": 87,
"Carol": 92,
}
for name, score := range scores {
fmt.Printf("%s scored %d\n", name, score)
}
// String iteration (runes, not bytes!)
text := "Hello, 世界"
for i, char := range text {
fmt.Printf("Position %d: %c (Unicode: %U)\n", i, char, char)
}
}
Real-World Examples and Use Cases
Processing HTTP Server Requests
Here's a realistic example of using for loops in a web server context:
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"time"
)
type LogEntry struct {
Timestamp time.Time `json:"timestamp"`
Method string `json:"method"`
Path string `json:"path"`
Status int `json:"status"`
}
func processLogs(logs []LogEntry) map[string]int {
statusCounts := make(map[string]int)
// Range loop for processing slice
for _, entry := range logs {
statusGroup := fmt.Sprintf("%dxx", entry.Status/100)
statusCounts[statusGroup]++
}
return statusCounts
}
func serverHealthCheck() {
endpoints := []string{
"http://api.example.com/health",
"http://db.example.com/ping",
"http://cache.example.com/status",
}
// Check each endpoint
for i, endpoint := range endpoints {
// Infinite loop with timeout
attempts := 0
for {
resp, err := http.Get(endpoint)
if err == nil && resp.StatusCode == 200 {
fmt.Printf("✓ Endpoint %d (%s) is healthy\n", i+1, endpoint)
resp.Body.Close()
break
}
attempts++
if attempts >= 3 {
fmt.Printf("✗ Endpoint %d (%s) failed after 3 attempts\n", i+1, endpoint)
break
}
fmt.Printf("Retrying endpoint %d, attempt %d...\n", i+1, attempts+1)
time.Sleep(time.Duration(attempts) * time.Second)
}
}
}
Concurrent Processing with Goroutines
For loops are essential when working with channels and goroutines:
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int) {
// Infinite loop reading from channel
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
time.Sleep(time.Millisecond * 100) // Simulate work
results <- job * 2
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
// Start 3 worker goroutines
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// Send 9 jobs
for j := 1; j <= 9; j++ {
jobs <- j
}
close(jobs)
// Collect results
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for a := 1; a <= 9; a++ {
result := <-results
fmt.Printf("Result: %d\n", result)
}
}()
wg.Wait()
}
Performance Comparisons and Benchmarks
Different loop patterns have varying performance characteristics. Here's a comparison of common iteration approaches:
Loop Type | Use Case | Performance | Memory Usage | Best For |
---|---|---|---|---|
Classic for loop | Numeric iteration | Fastest | Minimal | Index-based operations |
Range over slice | Element access | Very fast | Minimal | Processing slice elements |
Range over map | Key-value pairs | Fast | Minimal | Map processing |
Range over string | Unicode handling | Moderate | Low | Text processing |
Range over channel | Concurrent processing | Variable | Buffer dependent | Pipeline processing |
Here's a benchmark example you can run to see the differences:
package main
import (
"fmt"
"testing"
"time"
)
func BenchmarkClassicLoop(b *testing.B) {
slice := make([]int, 1000000)
for i := range slice {
slice[i] = i
}
b.ResetTimer()
for n := 0; n < b.N; n++ {
sum := 0
for i := 0; i < len(slice); i++ {
sum += slice[i]
}
}
}
func BenchmarkRangeLoop(b *testing.B) {
slice := make([]int, 1000000)
for i := range slice {
slice[i] = i
}
b.ResetTimer()
for n := 0; n < b.N; n++ {
sum := 0
for _, v := range slice {
sum += v
}
}
}
// Run with: go test -bench=.
Common Pitfalls and Best Practices
The Classic Range Variable Trap
This is probably the most common mistake Go developers make with for loops:
package main
import (
"fmt"
"time"
)
func main() {
// WRONG - captures loop variable by reference
items := []string{"apple", "banana", "cherry"}
fmt.Println("❌ Wrong way:")
for _, item := range items {
go func() {
time.Sleep(100 * time.Millisecond)
fmt.Printf("Processing: %s\n", item) // Will likely print "cherry" 3 times
}()
}
time.Sleep(500 * time.Millisecond)
fmt.Println("\n✅ Correct way:")
for _, item := range items {
go func(item string) { // Pass as parameter
time.Sleep(100 * time.Millisecond)
fmt.Printf("Processing: %s\n", item)
}(item)
}
time.Sleep(500 * time.Millisecond)
fmt.Println("\n✅ Alternative correct way:")
for _, item := range items {
item := item // Create new variable in loop scope
go func() {
time.Sleep(100 * time.Millisecond)
fmt.Printf("Processing: %s\n", item)
}()
}
time.Sleep(500 * time.Millisecond)
}
Range Over Maps: Order Isn't Guaranteed
Go deliberately randomizes map iteration order:
package main
import "fmt"
func main() {
scores := map[string]int{
"Alice": 95,
"Bob": 87,
"Carol": 92,
"Dave": 78,
}
// This order will vary between runs!
fmt.Println("Map iteration (order not guaranteed):")
for name, score := range scores {
fmt.Printf("%s: %d\n", name, score)
}
// If you need consistent order, sort the keys first
fmt.Println("\nSorted iteration:")
keys := make([]string, 0, len(scores))
for name := range scores {
keys = append(keys, name)
}
// You'd typically use sort.Strings(keys) here
for _, name := range keys {
fmt.Printf("%s: %d\n", name, scores[name])
}
}
Performance Best Practices
- Avoid unnecessary allocations: Pre-allocate slices when you know the size
- Use range for slices: It's optimized and more readable than index loops
- Be careful with large structs: Range creates copies, consider using pointers
- Don't modify slices during iteration: Can lead to unexpected behavior
// Good: Pre-allocate when size is known
results := make([]string, 0, len(input))
for _, item := range input {
results = append(results, process(item))
}
// Better: Direct assignment when possible
results := make([]string, len(input))
for i, item := range input {
results[i] = process(item)
}
// Careful with large structs
type LargeStruct struct {
data [1000]int
// ... other fields
}
items := []LargeStruct{...}
// This copies each struct (expensive!)
for _, item := range items {
process(item)
}
// This is more efficient
for i := range items {
process(&items[i])
}
Advanced Patterns and Integration
Loop Labels for Complex Control Flow
Go supports labeled breaks and continues for nested loops:
package main
import "fmt"
func main() {
// Finding the first pair that matches a condition
outer:
for i := 0; i < 5; i++ {
for j := 0; j < 5; j++ {
if i*j > 6 {
fmt.Printf("Found pair: i=%d, j=%d, product=%d\n", i, j, i*j)
break outer // Breaks out of both loops
}
}
}
// Using continue with labels
fmt.Println("\nSkipping certain combinations:")
outer2:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
continue outer2 // Continues outer loop
}
fmt.Printf("i=%d, j=%d\n", i, j)
}
}
}
Integration with Context for Cancellation
Modern Go applications often need cancellable loops:
package main
import (
"context"
"fmt"
"time"
)
func processWithTimeout(ctx context.Context, items []string) error {
for i, item := range items {
select {
case <-ctx.Done():
return fmt.Errorf("processing cancelled at item %d: %w", i, ctx.Err())
default:
// Process the item
fmt.Printf("Processing item %d: %s\n", i, item)
time.Sleep(200 * time.Millisecond) // Simulate work
}
}
return nil
}
func main() {
items := []string{"task1", "task2", "task3", "task4", "task5"}
// Create a context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
if err := processWithTimeout(ctx, items); err != nil {
fmt.Printf("Error: %v\n", err)
}
}
For loops in Go are deceptively simple but incredibly powerful. The key to mastering them is understanding when to use each variant and being aware of the common pitfalls. The Go team has put considerable effort into optimizing loop performance, so following idiomatic patterns usually gives you the best results. For more detailed information about Go's for statement and its optimizations, check out the official Go language specification and the Effective Go guide.
Whether you're processing HTTP requests, managing concurrent operations, or just iterating over data structures, these patterns will serve you well in building robust Go applications. The simplicity of having just one loop construct means less cognitive overhead and more consistent code across your projects.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.