Go: Control Structures, Shadowing, and Looping Techniques
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:
Classic C-style loop
for i := 0; i < 10; i++ { fmt.Println(i) }
Condition-only loop
i := 1 for i < 20 { fmt.Println(i) i++ }
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.
Subscribe to my newsletter
Read articles from Sahal Imran directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by