DEV Community

Cover image for Go Basics: Flow control
Abhinav Pandey
Abhinav Pandey

Posted on

Go Basics: Flow control

Flow Control

In the last article, we saw how to use functions. We also saw a preview of the for loop. We will explore the flow control syntax in depth in this article.

Go has the following flow control mechanisms:

  • if/else
  • switch
  • for
  • defer

If you are familiar with other languages, defer might be new to you and you may be missing while loops in this list.

Let's look at the syntax of each one of them.

Contents

  1. If/else
  2. Switch
  3. For
  4. Defer

If and else

What's similar to other languages:

if condition {
    // execute if condition is true
} else if condition {
    // execute if first condition is false but the second is true
} else {
    // execute if both conditions are false
}
Enter fullscreen mode Exit fullscreen mode

Notice that there are no braces around the conditions.

What's different from other languages:

if a:=rand.Intn(2); a==1 {
    return a
}  else {
    return a+1
}
Enter fullscreen mode Exit fullscreen mode

You can declare a variable before the condition and use it in the condition or in the if/else blocks.

Switch

Switch in Go is much more powerful than in other languages.

A simple swtich/case

switch a {
case 1:
    fmt.Println("a is 1")
case 2:
    fmt.Println("a is 2")
default:
    fmt.Println("a is neither 1 nor 2")
}
Enter fullscreen mode Exit fullscreen mode

The switch statement is used to execute different blocks of code depending on the value of a variable. Again, no braces around the value.
And if you've been coding in other languages (like Java), having no break must be a welcome change. There is no fall through in Go and only the first matching case is executed.

More flexible switch/case
Just like if/else, you can also declare a variable before the switch and use it in the switch/case blocks.

switch a := rand.Intn(10); a {
case 0, 1, 2, 3, 4:
    fmt.Println("a is 0, 1, 2, 3 or 4")
case 5, 6, 7, 8, 9:
    fmt.Println("a is 5, 6, 7, 8 or 9")
default:
    fmt.Println("a is neither 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9")
}
Enter fullscreen mode Exit fullscreen mode

Also notice how a case can be matched with multiple values. In Java, this was achieved with fall through.

Not just integers
You can also use switch case with strings, other types, and even expressions.

switch name {
case "John", "Paul", "George", "Ringo":
    fmt.Println("str is one of John, Paul, George or Ringo")
}

//switch/case with expression
switch time.Now().Weekday() {
case time.Saturday, time.Sunday:
    fmt.Println("It's the weekend")
default:
    fmt.Println("It's a weekday")
}
Enter fullscreen mode Exit fullscreen mode

This means that now you can use the switch statement for any type. Also notice that default is not mandatory.

Switch without a condition
You can also use the switch statement without a condition. Rather you can put your condition in the case block.

switch {
case a > b:
    fmt.Println("a is greater than b")
case a < b:
    fmt.Println("a is less than b")
default:
    fmt.Println("a is equal to b")
}
Enter fullscreen mode Exit fullscreen mode

This makes switch even more powerful because this construct is similar to else if ladders.
However, it does not give a performance boost because it will not use a jump table. It is only good for readability.

For loops

We saw an example of the for loop in the last article. Now let's look closer at the syntax.

A simple for

for i := 0; i < 10; i++ {
    fmt.Println(i)
}
Enter fullscreen mode Exit fullscreen mode

The for loop is used to execute a block of code a number of times. The above construct has almost no difference from the for loop in other languages.
The only difference is the shorthand variable declaration of the loop variable. I'm sure you are an expert with the shorthand variable declaration by now. It's everywhere.

All parts of the looping construct are optional

Infinite loops

// infinite loop
for {
    fmt.Println("loop")
}
Enter fullscreen mode Exit fullscreen mode

Optional update

for i := 0; i < 10; {
    i++;
    fmt.Println("loop")
}
Enter fullscreen mode Exit fullscreen mode

Optional initialization

i:=0
for ; i < 10; i++ {
    fmt.Println("loop")
}
Enter fullscreen mode Exit fullscreen mode

Without initialization or update

i:=0
for ;i < 10; {
    i++
    fmt.Println("loop")
}
Enter fullscreen mode Exit fullscreen mode

Don't even use the semicolons if its just a condition

i:=0
for i<10 {
    i++
    fmt.Println("loop")
}
Enter fullscreen mode Exit fullscreen mode

Wait a minute, isn't this a while loop? - YES

And that's why there is no explicit while loop in Go.

for with range

for i, v := range []int{1, 3, 5} {
    fmt.Println(i, v)
}
Enter fullscreen mode Exit fullscreen mode

The range statement is used to iterate over a slice or map. Here we iterate over a slice of integers.
We will look into the array syntax later but for now focus on the variables.

  • i is the index of the element in the slice
  • v is the value of the element in the slice So the above construct will print 0 1 and 1 3 and 2 5.

That's it about the for loop. Let's move on to something new now.

defer

The defer statement is used to execute a block of code at the end of the function.
This is sometimes called a "last resort" or "finalizer" because it is executed after the function returns.

A simple defer

func main() {
    defer fmt.Println("world")
    fmt.Println("hello")
}
Enter fullscreen mode Exit fullscreen mode

The above example will print hello and then world at the end of the function. It does not matter where the defer statement is placed. It will always be executed at the end of the function.

This must be a little strange but it is useful in many scenarios. For example:

  1. When you want to close a file or database connection.
  2. Logging the end of a function.
  3. Logging what values a variable has at the end of a function.

There are many other examples of defer in the Go language. You can read more about it here.

One point worth mentioning is that the defer statement is executed in LIFO order. If you have multiple defer statements, the last one will be executed first.
Let's see an example:

func main() {
    fmt.Println("counting")
    for i := 0; i < 10; i++ {
        defer fmt.Println(i)
    }
    fmt.Println("done")
}
Enter fullscreen mode Exit fullscreen mode

This will print counting and then done and then 9 and then 8 and so on. Similar to a stacktrace.


This should give you an idea about flow control in Go. In the next article, I will cover more syntactical elements of Go.

Thanks for reading. If you want to connect with me, you can find me on Twitter @abh1navv.

Discussion (0)