go has one loop keyword — for. no while, no do-while. but for is flexible enough to do everything. range is your best friend for collections.
go has exactly one loop keyword: for. it covers classic for, while, and infinite loops. less to remember, same power.
same as js minus the parentheses.
// js
for (let i = 0; i < 5; i++) { }
// go — no parentheses
for i := 0; i < 5; i++ {
fmt.Printf("fetching page %d...\n", i+1)
}condition only — no init, no post.
attempt := 0
maxRetries := 5
for attempt < maxRetries {
attempt++
fmt.Printf("attempt %d failed, retrying...\n", attempt)
}
fmt.Println("max retries reached")don't mutate your config values inside the loop. loop on a separate counter — maxRetries should still be 5 after the loop for reference.
no condition at all. equivalent to while (true) in js.
requestCount := 0
for {
if requestCount >= 5 { // use >= not == — safer
break
}
fmt.Printf("processing request %d\n", requestCount+1)
requestCount++
}
fmt.Println("server shutting down")use >= not == for exit conditions. if the counter somehow skips your exact value (e.g. jumps from 4 to 6), == never triggers and you have an infinite loop.
// break — exits the innermost loop only
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
break // exits inner loop, outer continues
}
fmt.Printf("page %d item %d\n", i, j)
}
}
// return — exits the entire function, always, no exceptions
for {
if done {
return // nothing after this runs in the function
}
}
// labeled break — exits a specific outer loop
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
break outer // exits the outer loop
}
}
}
fmt.Println("this runs after labeled break") // return would have killed thisskips the rest of the current iteration, moves to the next.
bannedID := 3
for i := 1; i <= 7; i++ {
if i == bannedID {
continue // skip banned user
}
fmt.Printf("processing user %d\n", i)
}range iterates over collections — slices, arrays, maps, strings, channels.
routes := []string{"/health", "/users", "/posts", "/auth"}
// both index and value
for i, v := range routes {
fmt.Printf("%d: %s\n", i, v)
}
// index only — valid without _
for i := range routes {
fmt.Println(i)
}
// value only — discard index with _
for _, v := range routes {
fmt.Println(v)
}you cannot do for v := range slice alone — range on a slice always returns index first, value second. if you name only one, go assumes it's the index.
// expr+1 is temporary — doesn't mutate
fmt.Printf("%d\n", requestCount+1) // requestCount unchanged
// requestCount++ mutates
requestCount++ // this changes the actual variable
// these are not the same thing — order inside loops matters
for {
if requestCount == 5 {
break
}
fmt.Printf("processing request %d\n", requestCount+1)
requestCount++
}
// when requestCount is 4:
// check: 4 == 5? no
// print: "processing request 5" (4+1)
// increment: requestCount = 5
// check: 5 == 5? yes — break
// "processing request 5" prints before the break — not after
// declare i, v but not use i — compile error
for i, v := range routes {
fmt.Println(v) // i declared but not used — won't compile
}
// fix
for _, v := range routes {
fmt.Println(v)
}