Go: Control Structures, Shadowing, and Looping Techniques

Sahal ImranSahal Imran
4 min read

In my recent exploration of Go, I’ve delved into core concepts that underpin effective Go programming. Here’s a summary of what I’ve learned.

Shadowing

Variable shadowing occurs when a variable in an inner block has the same name as a variable in an outer block. Shadowing can be useful but requires attention to avoid confusing results. Here’s a simple illustration

x := 10
if x > 5 {
    fmt.Println("outside:", x)
    x := 5 // shadows the outer x
    fmt.Println("inside:", x)
}
fmt.Println("outside:", x) // refers to the original x

Shadowing can even apply to package names or built-in types like int or true, which reside in a special “universe block.” By re-declaring them, I can shadow these identifiers

true := 10
fmt.Println(true) // now refers to the integer 10

While shadowing has specific uses, it’s easy to make mistakes by accidentally overwriting critical variables.

Control Structures: Go's Clean Approach to Conditionals and Loops

Go's if statements support inline initialization, enabling compact, scoped variable declarations

if n := rand.Intn(10); n == 0 {
    fmt.Println("very low number")
} else if n > 5 {
    fmt.Println("too big number", n)
} else {
    fmt.Println("fine number", n)
}

This scope restriction keeps code manageable and prevents unnecessary exposure of n outside the if block.

Looping

Go provides three ways to use for loops:

  1. Classic C-style loop

     for i := 0; i < 10; i++ {
         fmt.Println(i)
     }
    
  2. Condition-only loop

     i := 1
     for i < 20 {
         fmt.Println(i)
         i++
     }
    
  3. Infinite loop

     for {
         fmt.Println("hi")
     }
    

Breaking Out with continue and break

Go uses continue and break for exiting loops in a controlled manner. Here’s an example of FizzBuzz, implemented idiomatically with continue

for i := 1; i <= 100; i++ {
    if i%3 == 0 && i%5 == 0 {
        fmt.Println("FizzBuzz")
        continue
    }
    if i%3 == 0 {
        fmt.Println("Fizz")
        continue
    }
    if i%5 == 0 {
        fmt.Println("Buzz")
        continue
    }
    fmt.Println(i)
}

Using multiple continue statements creates a clean, readable code structure.

The Power of for range

In Go, for range is invaluable for iterating over arrays, slices, maps, and strings

evenVals := []int{2, 4, 6, 8, 10, 12}
for i, v := range evenVals {
    fmt.Println(i, v)
}

In a for range loop, values are copied, so modifying v doesn’t affect the original slice. To make changes, I used a slice of pointers

evenVals := []*int{new(int), new(int), new(int)}
*evenVals[0] = 2
*evenVals[1] = 4
*evenVals[2] = 6
for _, v := range evenVals {
    *v *= 2
}

Using pointers ensures that changes persist outside the loop.

Labels: Controlling Loop Execution

Go's labeled continue and break statements allow more control, which is useful for skipping or exiting nested loops

outer:
for _, sample := range []string{"hello", "apple_π"} {
    for i, r := range sample {
        fmt.Println(i, r, string(r))
        if r == 'l' {
            continue outer // skips to the next outer loop iteration
        }
    }
    fmt.Println()
}

Switch Statements

Go’s switch statements offer a concise alternative to chained if statements. Switch statements can evaluate expressions and support multiple cases

for _, word := range []string{"a", "cow", "smile"} {
    switch size := len(word); size {
    case 1, 2, 3, 4:
        fmt.Println(word, "is a short word")
    case 5:
        fmt.Println(word, "is exactly the right length:", len(word))
    default:
        fmt.Println(word, "is a long word!")
    }
}

The goto Statement: Sometimes Useful

Though rarely used, Go's goto can be helpful in specific cases to manage flow control within loops

a := rand.Intn(10)
for a < 100 {
    if a%5 == 0 {
        goto done
    }
    a = a*2 + 1
}
done:
fmt.Println("Loop exit:", a)

goto can improve readability in scenarios where a direct jump clarifies code flow.

Next Up

Now that I’ve covered Go's basics of control flow, loops, and shadowing, I’m excited to dive into functions next. I'll explore Go's approach to functions, including parameter passing, return values, anonymous functions, and closures.

0
Subscribe to my newsletter

Read articles from Sahal Imran directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sahal Imran
Sahal Imran