skip to content

Loop Forms and range

for is Go's only loop, and range decodes whatever you hand it: a slice index with a copy of the element, a map in randomized order, a string as runes, or since 1.22 a plain integer count.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

questions

4

In Go, what are the three non-range forms of for, and how do you write a while loop?

level: juniorimportance: must knowfreq 78%

answer

  1. one keyword covers every loop
  2. drop clauses and the shape changes
  3. no while keyword exists in Go
  4. condition-only is the while form
  5. for with no clauses runs forever

basics

~20 s

Go's for has three non-range forms: three-clause (init; condition; post), condition-only, and infinite (for with no clauses at all). The condition-only form is Go's while loop - the language has no while and no do-while keyword.

solid answer

~40 s

Go collapses every loop into one keyword. The three-clause form `for i := 0; i < n; i++ { }` is the C-style counter. Drop the init and post clauses and you get `for cond { }`, which is exactly a while loop; Go has no `while` keyword because this form already covers it. Drop the condition too and you get `for { }`, an infinite loop you leave with `break`, `return`, or a panic. There is no do-while either: you write `for { body; if !cond { break } }` when the body must run at least once. `break` leaves the innermost loop and `continue` jumps to the post statement and then re-tests the condition. Parentheses around the clauses are not used, and the braces are mandatory even for a one-line body.

code

go · 15 lines
go
// three-clause: init; condition; post
for i := 0; i < 10; i++ {
	_ = i
}

// condition-only: this is Go's while
n := 3
for n > 0 {
	n--
}

// infinite: leave with break, return or a panic
for {
	break
}

go deeper

for a junior

Be ready to write all three shapes on a whiteboard from memory and to say out loud that the condition-only form is Go's while. Knowing that braces are mandatory and parentheses are not is part of the expected answer.

for a middle

Explain the exact order of the init, condition, and post clauses, and what continue does differently in the three-clause form versus the condition-only form. Expect a follow-up on emulating do-while.

for a senior

Show the operational instinct: name how a condition-only loop spins forever when a body path skips the progress step, and how you would spot that pattern in review or in a hot-CPU incident.

for a principal

Argue the design tradeoff - one loop keyword lowers the reading cost of every codebase in the language at the price of a slightly clumsier do-while. That is the kind of subtraction Go makes on purpose, and it is worth being able to defend.

## One keyword instead of four Most C-family languages ship `for`, `while`, and `do ... while` as separate statements. Go ships one: `for`. Every loop in every Go program is a `for` statement, and the different shapes come from which clauses you leave out. This is a deliberate simplification - there is exactly one place to look when you read a loop, and exactly one keyword to learn. ### The three-clause form ```go for i := 0; i < len(rows); i++ { // body } ``` The three clauses are the **init statement**, the **condition**, and the **post statement**, separated by semicolons. The init statement runs once before the first iteration; the condition is evaluated before every iteration and the loop stops when it is false; the post statement runs after each iteration, before the condition is re-tested. A variable declared in the init clause with `:=` is scoped to the loop - it does not exist after the closing brace. The condition must be a boolean expression. Go has no truthiness: `for n { }` where `n` is an int does not compile. That is a deliberate barrier against a whole family of C bugs. ### The condition-only form: Go's while Omit the init and post clauses (and their semicolons) and you get: ```go for scannerHasMore { // body } ``` This is a `while` loop in every meaningful sense: the condition is tested before the first iteration, so the body may run zero times. Because this form exists, Go's designers saw no reason to add a `while` keyword. Reaching for `while` in Go source is a compile error, not a style problem - `while` is not a keyword at all, so the compiler reads it as an identifier and the statement fails to parse. This is the form you use when the number of iterations is not known in advance: reading until an input is exhausted, retrying until a condition flips, draining a work list until it is empty. ### The infinite form Omit everything: ```go for { // runs until break, return, or a panic } ``` `for { }` is a legal and idiomatic Go statement, not a syntax error. It is the standard shape for a service main loop, a retry loop, or a state machine that decides internally when to stop. You leave it with `break`, with `return` from the enclosing function, or by panicking. The compiler also knows a `for { }` with no `break` never completes, which is why code after such a loop can be unreachable without a missing-return complaint. ### There is no do-while Go has no statement that tests the condition after the body. The idiom is: ```go for { body() if !keepGoing { break } } ``` The body runs at least once and the exit test sits where you can see it. Some people write the test at the top with a first-iteration flag; that is worse, because the flag is state a reader has to track. ### break and continue `break` ends the innermost enclosing `for` immediately. `continue` skips the rest of the body and moves on: in the three-clause form it runs the post statement and then re-tests the condition; in the condition-only form it goes straight back to the condition. Both are unlabeled by default and act on the loop they sit in. ### The failure this form invites The condition-only form has one classic way to go wrong: nothing in the body changes what the condition reads. In a batch job that walks a directory of CSV files, `for len(pending) > 0 { ... }` spins forever if a parse failure takes the branch that never removes the file from `pending`, and a retry loop written as `for attempts < maxRetries { doWork() }` never terminates because nobody increments `attempts`. The loop is not hung on anything - it is running flat out, one core pinned at 100%. When you review a condition-only loop, the question to ask is: which statement in this body is guaranteed to move the condition towards false, and does every path through the body reach it? The three-clause form is more resistant to this because the post statement makes the progress step syntactically part of the loop header, where it is hard to omit and hard to skip - `continue` still runs it. ### Style notes Braces are always required, even for a one-statement body, and the opening brace is on the same line - gofmt enforces both. Parentheses around the clauses are not written. The empty body `for cond { }` is legal but usually a smell; if you truly want to just advance, say so with a comment.

  • How do you write a do-while loop in Go?
    You write an infinite loop with the test at the bottom: `for { body(); if !cond { break } }`. Go has no do-while statement, so the body-runs-at-least-once shape is expressed with `for { }` plus an explicit `break`. Keeping the exit test at the end of the body, rather than hiding it behind a first-iteration flag, is what makes it readable.
  • What do break and continue do inside a three-clause for loop?
    `break` ends the innermost loop immediately and control moves to the statement after it. `continue` abandons the rest of the body but does not skip the post statement - it runs the post statement and then re-evaluates the condition. That distinction matters: a `continue` in a three-clause loop still advances the counter, while a `continue` in a condition-only loop advances nothing unless the body already did.
  • A batch job's condition-only for loop pins a CPU and never finishes. What do you look for first?
    Find the variable the condition reads and check every path through the body for a statement that moves it towards false. The usual causes are a counter that is never incremented, a work list an error path never shrinks, or a `continue` that jumps over the one line that made progress. Adding the progress step to a three-clause header, or asserting progress on each pass, prevents the reoccurrence.

One dial with three settings rather than three separate switches: turn off the counter and you have a while, turn off the condition too and you have a forever loop.

saying these in an interview costs you the question

  • Claims Go has a while keyword
  • Says for with no clauses is a syntax error
  • Thinks Go has a do-while statement
  • Wraps the for clauses in parentheses as required syntax
  • Believes a non-boolean condition like for n is allowed
open as a page

In Go, why does ranging over a slice of structs with for _, r := range rows { r.count++ } leave rows unchanged?

level: middleimportance: must knowfreq 66%

basics

~20 s

range assigns a copy of each element into r, so r.count++ increments a copy that is thrown away at the end of the iteration. To modify the slice, index into it: for i := range rows { rows[i].count++ }.

open as a page

In Go, what does range yield for a slice, a map, a string, and an integer?

level: middleimportance: must knowfreq 74%

basics

~20 s

A slice yields the index and a copy of the element; a map yields a key and its value in unspecified order; a string yields the byte offset and the decoded rune; an integer n yields the values 0 through n-1.

open as a page

If you append to a Go slice while ranging over it, does the loop visit the newly appended elements?

level: seniorimportance: should knowfreq 44%

basics

~20 s

No. range evaluates the slice expression once and fixes the iteration count from its length before the first pass, so the loop runs exactly the original len(s) times. Appended elements are in the slice afterwards but are never visited.

open as a page