How to install go-control-flow
npx skills add https://github.com/cxuu/golang-skills --skill go-control-flowFull instructions (SKILL.md)
Source of truth, from cxuu/golang-skills.
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).
Go Control Flow
Resource Routing
references/SWITCH-PATTERNS.md- Read when using switch statements, type switches, fallthrough, or labeled breaks.references/BLANK-IDENTIFIER.md- Read when using_, blank imports, unused compile-time assertions, or intentional discards.
If with Initialization
if and switch accept an optional initialization statement. Use it to scope
variables to the conditional block:
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:
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:
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:
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:
- The declaration is in the same scope as the existing
v - The value is assignable to
v - 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:
// 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:
// 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:
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 —
iis 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:
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:
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.
Route compile-time interface assertions to go-interfaces.
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 | Route to go-interfaces |
Related Skills
- Error flow: See go-error-handling when structuring guard clauses, early returns, or error-first patterns
- Type switches: See go-interfaces when using type switches, the comma-ok idiom, or interface satisfaction checks
- Nesting reduction: See go-style-core when reducing nesting depth or resolving formatting questions
- Variable scoping: See go-declarations when using if-init,
:=redeclaration, or reducing variable scope
Related skills
More from cxuu/golang-skills and the wider catalog.

go-data-structures
Use when working with Go slices, maps, or arrays — choosing between new and make, using append, declaring empty slices (nil vs literal for JSON), implementing sets with maps, and copying data at boundaries. Also use when building or manipulating collections, even if the user doesn't ask about allocation idioms. Does not cover concurrent data structure safety (see go-concurrency).

go-defensive
Use when hardening Go code at API boundaries — copying slices/maps, verifying interface compliance, using defer for cleanup, time.Time/time.Duration, or avoiding mutable globals. Also use when reviewing for robustness concerns like missing cleanup or unsafe crypto usage, even if the user doesn't mention "defensive programming." Does not cover error handling strategy (see go-error-handling).

go-documentation
Use when writing or reviewing documentation for Go packages, types, functions, or methods. Also use proactively when creating new exported types, functions, or packages, even if the user doesn't explicitly ask about documentation. Does not cover code comments for non-exported symbols (see go-style-core).

go-error-handling
Use when writing Go code that returns, wraps, or handles errors — choosing between sentinel errors, custom types, and fmt.Errorf (%w vs %v), structuring error flow, or deciding whether to log or return. Also use when propagating errors across package boundaries or using errors.Is/As, even if the user doesn't ask about error strategy. Does not cover panic/recover patterns (see go-defensive).

go-functional-options
Use when designing a Go constructor or factory function with optional configuration — especially with 3+ optional parameters or extensible APIs. Also use when building a New* function that takes many settings, even if they don't mention "functional options" by name. Does not cover general function design (see go-functions).

go-interfaces
Use when defining or implementing Go interfaces, designing abstractions, creating mockable boundaries for testing, or composing types through embedding. Also use when deciding whether to accept an interface or return a concrete type, or using type assertions or type switches, even if the user doesn't explicitly mention interfaces. Does not cover generics-based polymorphism (see go-generics).