Control Structures

The Infinite Loop That Wasn't

My fourth day writing Go, I deployed code to staging that immediately crashed:

func processQueue() {
    for {
        item := queue.Pop()
        if item == nil {
            break
        }
        process(item)
    }
}

The queue was empty. queue.Pop() returned nil. The loop broke immediately. In production, this would have stopped processing forever once the queue drained.

Coming from Python's while True:, I didn't understand Go's for loop nuances. The fix required understanding Go's approach to control structures - which is both simpler and more powerful than I expected.

This article covers the control flow lessons I learned by breaking things in staging.


The for Loop: Go's Only Loop

Go has one loop construct: for. No while, no do-while, no foreach. Just for.

This seemed limiting until I realized for does everything.

Traditional For Loop

Components:

  1. Init: i := 0 (executed once)

  2. Condition: i < 10 (checked before each iteration)

  3. Post: i++ (executed after each iteration)

While-style Loop

Infinite Loop

This is where my queue processor failed. The pattern should have been:

Range Loop (foreach equivalent)

Range gotcha that bit me:


Break and Continue

Break: Exit Loop

Continue: Skip to Next Iteration

Labels: Break from Nested Loops

Real code from my CSV processor:


If Statements

Basic If

If-Else

If-Else If

If with Initialization (Powerful!)

This is a Go feature I use constantly:

Real example from my file processor:

This keeps variables scoped tightly and prevents namespace pollution.


Switch Statements

Go's switch is more powerful than C/Java switch - no fall-through by default!

Basic Switch

No break needed! Each case automatically breaks.

Switch with Initialization

Expression-less Switch (like if-else chain)

This is incredibly useful:

Real example from my request router:

Much cleaner than nested if-else!

Type Switch

For working with interfaces (we'll cover interfaces in detail later):

Fall-through (explicit)

If you need C-style fall-through, use fallthrough:

I've used fallthrough exactly once in production. It's rare.


The defer Statement

defer schedules a function to run when the surrounding function returns. This is one of Go's most elegant features.

Basic Defer

Multiple Defers (LIFO order)

Defers execute in Last-In-First-Out order (like a stack).

Real-World Usage: Resource Management

Database transactions:

Mutex unlocking:

Timing functions:

Defer Gotcha: Loop Variables

This bug cost me an hour:

In the first version, all files stay open until the parent function returns (file descriptor leak!).


Practical Example: Request Retry Logic

Real code from my API client:

This uses:

  • for with counter

  • if with initialization

  • switch for status codes

  • defer for resource cleanup

  • continue for retries


Common Patterns

Safe Integer Parsing

Early Return Pattern

Exponential Backoff


Your Challenge

Build a simple game: Guess the number

Bonus: Add difficulty levels, max attempts, play again logic.


Key Takeaways

  1. One loop to rule them all: for does everything

  2. Range is powerful: Clean iteration over collections

  3. If with init: Scope variables tightly

  4. Switch without fall-through: No break needed by default

  5. Expression-less switch: Better than if-else chains

  6. Defer for cleanup: Resources close automatically

  7. Defer is LIFO: Last deferred executes first


What I Learned

The simplicity of Go's control structures is deceptive. Having only for seemed limiting until I realized:

  • No confusion about for vs while vs foreach

  • Switch without fall-through prevents bugs

  • Defer makes resource management bulletproof

  • If with initialization keeps code clean

The queue bug taught me to think differently about loops. In Python, I'd write while True. In Go, I write for { } and think about the exit conditions more carefully.


Next: Functions and Methods

In the next article, we'll explore Go's approach to functions: multiple return values, named returns, methods on types, and how functions are first-class citizens. You'll see how this changes the way you structure code.

Last updated