
Go Control Flow
- 891 installs
- 137 repo stars
- Updated June 20, 2026
- cxuu/golang-skills
go-control-flow is a Go language skill that teaches blank identifier `_` patterns for safe error handling, side-effect imports, and compile-time interface checks for developers who write idiomatic Go services and CLIs.
About
go-control-flow is a Go coding skill from cxuu/golang-skills that documents when and how to use the blank identifier `_` in real Go programs. The skill covers discarding unwanted values from multi-assignment expressions, importing packages purely for side effects, and asserting interface compliance at compile time without runtime overhead. It also warns against silently discarding errors that cause nil-pointer panics, with examples using `os.Stat`, `if _, err :=` patterns, and documented intentional ignores. Developers reach for go-control-flow when reviewing Go error-handling code, refactoring imports, or enforcing interface contracts during backend or CLI implementation.
- Safely discards values from multi-return functions while preserving error checks
- Enables clean side-effect package imports using the blank identifier
- Performs compile-time interface compliance verification with nil pointer checks
- Prevents nil-pointer panics by never silently discarding errors
- Documents intentional error ignores with explanatory comments
Go Control Flow by the numbers
- 891 all-time installs (skills.sh)
- +39 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #452 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cxuu/golang-skills --skill go-control-flowAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 891 |
|---|---|
| repo stars | ★ 137 |
| Security audit | 3 / 3 scanners passed |
| Last updated | June 20, 2026 |
| Repository | cxuu/golang-skills ↗ |
How do you use Go's blank identifier safely?
Master Go's blank identifier patterns for safe error handling, side-effect imports, and compile-time interface checks.
Who is it for?
Go backend and CLI developers who want compile-time safety patterns and disciplined error handling with the blank identifier.
Skip if: Developers not writing Go, or teams that need database schema, API design, or deployment guidance instead of language idioms.
When should I use this skill?
The user edits Go code involving multi-value returns, blank imports, interface compliance checks, or questionable `_` error discards.
What you get
Idiomatic Go snippets using `_` for discarded values, side-effect imports, interface checks, and documented error-ignore cases.
- idiomatic Go code snippets
- documented error-handling decisions
Files
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 xIndent 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 errA 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:
// 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 closedKey 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
Blank Identifier Patterns
The blank identifier _ serves multiple roles in Go: discarding unwanted values, importing packages for side effects, and verifying interface compliance at compile time.
---
Multiple Assignment
Use _ to discard unwanted values from multi-value expressions:
if _, err := os.Stat(path); os.IsNotExist(err) {
fmt.Printf("%s does not exist\n", path)
}Never Discard Errors Carelessly
Silently discarding an error invites nil-pointer panics:
// Bad: ignoring error will crash if path doesn't exist
fi, _ := os.Stat(path)
if fi.IsDir() { ... } // nil pointer dereferenceIf you truly don't need the error, document why:
_ = logger.Sync() // best-effort flush; error is non-actionable---
Import for Side Effect
Import a package solely for its init() side effects using the blank identifier:
import _ "net/http/pprof" // registers HTTP handlers
import _ "image/png" // registers PNG decoderThis is commonly used to register drivers, codecs, or debug handlers that wire themselves into a registry during init().
---
Interface Satisfaction Checks
Blank identifiers are also used in compile-time interface assertions, but that rule is owned by go-interfaces. Use this reference for blank-identifier mechanics; route assertion placement, need, and validation decisions to the interface skill.
---
Quick Reference
| Pattern | Syntax |
|---|---|
| Discard value | _, err := f() |
| Discard in if-init | if _, err := f(); err != nil { } |
| Side-effect import | import _ "pkg" |
| Interface check | Route to go-interfaces |
Switch Patterns
Detailed patterns for Go switch statements, including expression-less switches, comma cases, break behavior, and labeled breaks.
---
No Automatic Fallthrough
Go switch cases do not fall through by default (unlike C/Java). Each case body implicitly breaks. Use fallthrough only when explicitly needed — it is rare in idiomatic Go.
switch n {
case 1:
fmt.Println("one")
// no fallthrough — next case is NOT executed
case 2:
fmt.Println("two")
}---
Expression-less Switch
A switch with no expression switches on true. Use it for clean if-else-if chains when comparing a single variable against multiple conditions:
func unhex(c byte) byte {
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}---
Comma-Separated Cases
Multiple values can share a single case body using commas — no need for fallthrough:
func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
}
return false
}---
Break with Labels
break inside a switch terminates only the switch, not an enclosing for loop. Use a label to break out of the loop:
Loop:
for n := 0; n < len(src); n += size {
switch {
case src[n] < sizeOne:
break // breaks switch only
case src[n] < sizeTwo:
if n+1 >= len(src) {
break Loop // breaks out of for loop
}
}
}Another common pattern — breaking a range loop from inside a switch:
Loop:
for _, v := range items {
switch v.Type {
case "done":
break Loop // breaks the for loop
case "skip":
break // breaks only the switch
}
}Rule of thumb: Whenever you have a switch inside a for and need to exit the loop from a case, always use a labeled break.
---
Type Switches
For type switches (switch v := x.(type)), see go-interfaces: Type Switch.
---
Quick Reference
| Pattern | Syntax |
|---|---|
| Expression-less switch | switch { case cond: } |
| Comma cases | case 'a', 'b', 'c': |
| No fallthrough | Default; use fallthrough keyword if needed |
| Break switch only | break inside case |
| Break enclosing loop | break Label with labeled for |
Related skills
How it compares
Pick go-control-flow for Go-specific `_` idioms rather than general debugging or architecture skills.
FAQ
What does the blank identifier do in Go?
go-control-flow explains that `_` discards unwanted values from multi-return expressions, enables side-effect-only imports, and verifies interface compliance at compile time without allocating variables.
When should you not use `_` for errors in Go?
go-control-flow warns that discarding errors from calls like `os.Stat` can cause nil-pointer dereferences; the skill recommends handling or explicitly documenting any intentional error ignore.
Is Go Control Flow safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.