Use when writing conditionals, loops, or switch statements in Go — including if with initialization, early returns, for loop forms, range, switch, type switches, and blank identifier patterns. Also use when writing a simple if/else or for loop, even if the user doesn't mention guard clauses or variable scoping. Does not cover error flow patterns (see go-error-handling).
---
name: go-control-flow
description: Use when writing conditionals, loops, or switch statements in Go — including if with initialization, early returns, for loop forms, range, switch, type switches, and blank identifier patterns. Also use when writing a simple if/else or for loop, even if the user doesn't mention guard clauses or variable scoping. Does not cover error flow patterns (see go-error-handling).
license: Apache-2.0
metadata:
sources: "Effective Go, Google Style Guide"
---
# Go Control Flow
> Read [references/SWITCH-PATTERNS.md](references/SWITCH-PATTERNS.md) when using switch statements, type switches, or break with labels
> Read [references/BLANK-IDENTIFIER.md](references/BLANK-IDENTIFIER.md) when using `_`, blank identifier imports, or compile-time interface checks
---
## If with Initialization
`if` and `switch` accept an optional initialization statement. Use it to scope
variables to the conditional block:
```go
if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
}
```
If you need the variable beyond a few lines after the `if`, declare it
separately and use a standard `if` instead:
```go
x, err := f()
if err != nil {
return err
}
// lots of code that uses x
```
## Indent Error Flow (Guard Clauses)
When an `if` body ends with `break`, `continue`, `goto`, or `return`, omit the
unnecessary `else`. Keep the success path unindented:
```go
f, err := os.Open(name)
if err != nil {
return err
}
d, err := f.Stat()
if err != nil {
f.Close()
return err
}
codeUsing(f, d)
```
Never bury normal flow inside an `else` when the `if` already returns.
---
## Redeclaration and Reassignment
The `:=` short declaration allows redeclaring variables in the same scope:
```go
f, err := os.Open(name) // declares f and err
d, err := f.Stat() // declares d, reassigns err
```
A variable `v` may appear in a `:=` declaration even if already declared,
provided:
1. The declaration is in the **same scope** as the existing `v`
2. The value is **assignable** to `v`
3. At least **one other variable** is newly created by the declaration
### Variable Shadowing
**Warning**: If `v` is declared in an outer scope, `:=` creates a **new**
variable that shadows it — a common source of bugs:
```go
// Bug: ctx inside the if block shadows the outer ctx
if *shortenDeadlines {
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
}
// ctx here is still the original — the shadowed ctx didn't escape
// Fix: use = instead of :=
var cancel func()
ctx, cancel = context.WithTimeout(ctx, 3*time.Second)
```
---
## For Loops
Go's `for` is its only looping construct, unifying `while`, `do-while`, and
C-style `for`:
```go
// Condition-only (Go's "while")
for x > 0 {
x = process(x)
}
// Infinite loop
for {
if done() { break }
}
// C-style three-component
for i := 0; i < n; i++ { ... }
```
### Range
`range` iterates over slices, maps, strings, and channels:
```go
for i, v := range slice { ... } // index + value
for k, v := range myMap { ... } // key + value (non-deterministic order)
for i, r := range "héllo" { ... } // byte index + rune (not byte)
for v := range ch { ... } // receives until channel closed
```
**Key rules:**
- Range over strings yields **runes**, not bytes — `i` is the byte offset
- Range over maps has **non-deterministic order** — don't rely on it
- Use `_` to discard the index or value: `for _, v := range slice`
### Parallel Assignment
Go has no comma operator. Use parallel assignment for multiple loop variables:
```go
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}
```
`++` and `--` are statements, not expressions — they cannot appear in parallel
assignment.
---
## Switch: Labeled Break
`break` inside a `switch` within a `for` loop only breaks the switch.
Use a labeled `break` to exit the enclosing loop:
```go
Loop:
for _, v := range items {
switch v.Type {
case "done":
break Loop // breaks the for loop
}
}
```
For type switches, see **go-interfaces**: Type Switch.
---
## The Blank Identifier
**Never discard errors carelessly** — a nil dereference panic may follow.
Verify interface compliance at compile time: `var _ io.Writer = (*MyType)(nil)`.
See **go-interfaces** for the interface satisfaction check pattern.
---
## Quick Reference
| Pattern | Go Idiom |
|---------|----------|
| If initialization | `if err := f(); err != nil { }` |
| Early return | Omit `else` when if body returns |
| Redeclaration | `:=` reassigns if same scope + new var |
| Shadowing trap | `:=` in inner scope creates new variable |
| Parallel assignment | `i, j = i+1, j-1` |
| Expression-less switch | `switch { case cond: }` |
| Comma cases | `case 'a', 'b', 'c':` |
| No fallthrough | Default behavior (explicit `fallthrough` if needed) |
| Break from loop in switch | `break Label` |
| Discard value | `_, err := f()` |
| Side-effect import | `import _ "pkg"` |
| Interface check | `var _ Interface = (*Type)(nil)` |
---
## Related Skills
- **Error flow**: See [go-error-handling](../go-error-handling/SKILL.md) when structuring guard clauses, early returns, or error-first patterns
- **Type switches**: See [go-interfaces](../go-interfaces/SKILL.md) when using type switches, the comma-ok idiom, or interface satisfaction checks
- **Nesting reduction**: See [go-style-core](../go-style-core/SKILL.md) when reducing nesting depth or resolving formatting questions
- **Variable scoping**: See [go-declarations](../go-declarations/SKILL.md) when using if-init, `:=` redeclaration, or reducing variable scope
Creator's repository · cxuu/golang-skills
License: Apache-2.0