Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 4

Go Control Flow

Recommended resources

Welcome to the next lesson in our journey through Go! In our previous session, you learned how to manage collections of data using Go's arrays, slices, and maps. We also got a sneak peek at the for...range loop for iteration and the if statement for conditional logic in our code example.

Today, we will formalize and expand on that by diving deep into Go's control flow structures: if, for, and switch. Given your extensive background in programming with languages like Python and TypeScript, we won't be covering the basic theory of what a loop or a conditional is. Instead, we'll focus squarely on the specifics of Go's syntax, its unique features, and the idiomatic patterns that constitute "the Go way" of controlling program execution.

The if Statement: More Than Just a Condition

You're already familiar with if-else statements. Go's version has a couple of syntactic distinctions: parentheses around the condition are not used, but curly braces for the body are always mandatory, even for single-line statements.

if x > 0 {
    fmt.Println("x is positive")
} else {
    fmt.Println("x is not positive")
}

The most significant and idiomatic feature of Go's if statement is its ability to include a short initialization statement. This initializer executes before the condition is evaluated, and any variables declared in it are scoped to the if and any else blocks.

This pattern is extremely common in Go, especially for functions that return a value and an error. It allows you to handle potential errors compactly and keep the scope of error variables tightly contained.

Let's watch a short video segment that demonstrates this pattern in the context of error handling.

Learn GO Fast: Full Tutorial

This clip from Alex Mux's "Learn GO Fast" tutorial shows the if-init pattern in a very practical context.

Watch the section from this timestamp. Pay close attention to how the intDivision function returns both a result and an error, and how the if statement in the main function captures and checks that error in a single, clean line.

This if err := ...; err != nil construct is one of the most fundamental idioms in Go. You will write it constantly.

The official "Effective Go" document, which is the canonical guide to writing idiomatic Go, elaborates on this and another key pattern: structuring your code to have a "happy path" that flows down the page, handling errors with early returns and thus avoiding nested else blocks.

Effective Go

Let's read two key sections from the "Control structures" part of "Effective Go".

First, read about the initialization statement, which formalizes the pattern you just saw in the video. Next, read the following paragraphs on the idiomatic omission of else. This "guard clause" style is strongly preferred in Go for improving readability.

This style of minimizing else clauses leads to code that is flatter and often easier to follow, as the main logic is not indented inside multiple levels of conditional blocks.


The for Loop: Go's Unified Looping Construct

Coming from Python and TypeScript, you're used to having different keywords for different kinds of loops, like for for iteration and while for conditional looping. Go simplifies this by providing a single, powerful for keyword that handles all looping scenarios.

This article provides a direct comparison between Python and Go loops, which should resonate well with your background.

From Python to Go | Towards Data Science

This article directly maps Python concepts to Go.

Read the section titled Loops and Control Flow. It does an excellent job of showing how Go's single for loop can be used to replicate both Python's for and while loops.

As you've seen, the for loop has a few forms:

  1. The while form: Used when you want to loop as long as a condition is true.

    i := 0
    for i < 5 {
        fmt.Println(i)
        i++
    }
    
  2. The traditional C-style form: With an initializer, a condition, and a post-statement. This is useful for counter-based loops.

    for j := 0; j < 5; j++ {
        fmt.Println(j)
    }
    
  3. The for...range form: You saw this in our last lesson for iterating over slices and maps.

    for index, value := range mySlice {
        //...
    }
    
  4. The infinite loop: This is used for tasks that need to run continuously, often in conjunction with a break statement.

    for {
        // This will loop forever until a 'break' or 'return'.
    }
    

The following video provides a good visual summary of these different forms and also introduces the break and continue keywords, which work just as you'd expect.

Golang Tutorial #4 - Control Structures/Statements (If, Else, For & Switch)

This video from Esoteric Tech walks through the different syntaxes for the for loop.

Watch the segment from this timestamp. It covers the C-style loop, using break to exit early, continue to skip an iteration, and the while-style loop.

One subtle but important detail for an experienced developer: in Go, ++ and -- are statements, not expressions. This means you cannot do something like x := i++. They can only be used on their own line or in the post-statement of a for loop.


The switch Statement: Powerful and Flexible

Go's switch statement is significantly more powerful and flexible than its counterpart in many other C-family languages. In Go, it's often used in places where you might write a long if-else-if-else chain in Python.

Key features of Go's switch:

  • No automatic fall-through: Unlike JavaScript/TypeScript, a matching case in Go does not "fall through" to the next one. A break is implicit. If you need this behavior (which is rare), you must use the fallthrough keyword explicitly.
  • Cases can be expressions: The cases don't have to be constants. They are evaluated top-to-bottom, and the first matching case is executed.
  • Expressionless switch: You can omit the expression after the switch keyword. It's equivalent to switch true, which makes it a very clean way to write complex if-else logic.

This video clip gives a great, concise demonstration of both a standard conditional switch and an expressionless switch.

Learn GO Fast: Full Tutorial

Let's return to the "Learn GO Fast" video for a look at switch.

Watch the segment from this clip. Note how the first example replaces the previous if-else-if chain, and the second example checks the value of the remainder variable.

The expressionless switch is a particularly elegant Go idiom. For example:

// Using if-else-if
if score > 90 {
    grade = "A"
} else if score > 80 {
    grade = "B"
} else {
    grade = "C"
}

// Using an expressionless switch - often cleaner
switch {
case score > 90:
    grade = "A"
case score > 80:
    grade = "B"
default:
    grade = "C"
}

Let's Write Some Code

Let's update the word-counting code from our previous lesson to incorporate these new control flow structures. This example will use a for...range loop, an if statement with an initializer, an expressionless switch, and a traditional C-style for loop.

Replace the content of your main.go file with this code:

package main

import "fmt"

func main() {
	wordCounts := map[string]int{
		"go":       3,
		"is":       1,
		"a":        1,
		"great":    1,
		"language": 1,
		"has":      1,
		"simple":   1,
		"syntax":   1,
	}

	wordsToAnalyze := []string{"go", "python", "is", "simple"}

	// Use a for...range loop to iterate over the words we want to check.
	for _, word := range wordsToAnalyze {
		
		// Use an 'if' statement with an initializer.
		// The 'count' and 'ok' variables are scoped only to this if/else block.
		if count, ok := wordCounts[word]; ok {
			fmt.Printf("Analyzing '%s' (count: %d): ", word, count)
			
			// Use an expressionless 'switch' to categorize the word frequency.
			// This is much cleaner than a long if-else-if chain.
			switch {
			case count > 2:
				fmt.Println("This is a very common word in the text.")
			case count > 1:
				fmt.Println("This is a somewhat common word.")
			default:
				fmt.Println("This is a less common word.")
			}
		} else {
			// This 'else' block executes if the 'ok' from the map lookup was false.
			fmt.Printf("The word '%s' was not found in our text.\n", word)
		}
	}
	
	// Demonstrate a C-style for loop for a simple countdown.
	fmt.Println("\n--- Countdown ---")
	for i := 3; i > 0; i-- {
		fmt.Println(i)
	}
	fmt.Println("Blast off!")
}

Run this program with go run .. Observe how the different control structures work together. The if-init statement neatly handles the map lookup, the switch cleanly categorizes the results, and the final for loop shows the classic C-style syntax in action.


Conclusion

You have now gained a solid understanding of Go's primary control flow statements and, more importantly, the idiomatic ways they are used.

Key Takeaways:

  • if statements in Go are concise. The if-init form (if v := f(); condition) is a powerful idiom for creating tightly scoped variables, especially for error handling.
  • The single for loop is a versatile tool that handles all looping needs, from while-style conditions to C-style iteration and for...range over collections.
  • Go's switch statement is a flexible and clean alternative to complex if-else chains, thanks to expressionless switches and cases that are evaluated as expressions.
  • Go's syntax (no parentheses, mandatory braces) and idioms (like the "happy path" and if-init) promote a consistent, readable style.

In our next lesson, we will move into the first module on "Structuring Data with Structs and Methods." You'll learn how to define your own custom data types using structs, which is Go's way of grouping data together, analogous to classes or objects in other languages. This will be the first step towards building more complex and organized applications.