Basic Control Flows

The control flow code blocks in Go are much like other popular programming languages, but there are also many differences. This article will show these similarities and differences.

An Introduction Of Control Flows In Go

There are three kinds of basic control flow code blocks in Go: There are also some control flow code blocks which are related to some certain kinds of types in Go.

Like many other popular languages, Go also supports break, continue and goto code execution jump statements. Besides these, there is a special code jump statement in Go, fallthrough.

Among the six kinds of control flow blocks, execept the if-else control flow, the other five are call breakable control flow blocks. We can use break statements to make executions jump out of breakable control flow blocks.

for and for-range loop blocks are called loop control flow blocks. The other four kinds of control flow blocks can all be called as conditional execution control flow blocks. We can use continue statements to end a loop step in advance in a loop control flow block.

Above mentioned control flow statements are all the ones in narrow sense. The mechanisms introduced in the next article (goroutines, deferred function calls and panic/recover) and the concurrency synchronization techniques introduced in the later article concurrency synchronization overview can be viewed as control flow statements in broad sense.

Only the basic control flow code blocks and code jump statements will be explained in the current article, other ones will be explained in many other Go 101 articles later.

if-else Control Flow Blocks

The full form of a if-else code block is like
if InitSimpleStatement; Condition {
	// do something
} else {
	// do something
}

if and else are keywords. Like many other programming languages, the else-branch part is optional.

The InitSimpleStatement portion is also optional. It must a simple statement. If it is absent, we can view it as a blank statement. In practice, InitSimpleStatement is often a short variable declaration or a pure assignment. Condition must be an expression which result is a boolean value. The Condition portion can be enclosed in a pair of () or not, but it can't be enclosed together with the InitSimpleStatement portion.

If the InitSimpleStatement in a if-else block is present, it will be executed before executing other statements in the if-else block. If the InitSimpleStatement is absent, then the semicolon following it is optional.

Example:
package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	rand.Seed(time.Now().UnixNano())
	
	if n := rand.Int(); n%2 == 0 {
		fmt.Println(n, "is an even number.")
	} else {
		fmt.Println(n, "is an odd number.")
	}
	
	n := rand.Int() % 2 // this n is not the above n.
	if n % 2 == 0 {
		fmt.Println("An even number.")
	}
	
	if ; n % 2 != 0 {
		fmt.Println("An odd number.")
	}
}

Each if-else control flow forms one implicit code block, one if-branch explicit code block and one optional else-branch code block. The else-branch code block can be implicit if the corresponding else is followed by another if-else code block. The two branch code blocks are both nested in the implicit code block. If the InitSimpleStatement in a if-else code block is a short variable declaration, then the declared variables will be viewed as declared in the nesting implicit code block.

Example:
package main

import (
	"fmt"
	"time"
)

func main() {
	if h := time.Now().Hour(); h < 12 {
		fmt.Println("Now is AM time.")
	} else if h > 19 {
		fmt.Println("Now is evening time.")
	} else {
		fmt.Println("Now is afternoon time.")
		h := h // a new "h" variable shadows the above one.
		_ = h
	}
	
	// h is not visible here.
}

for Loop Control Flow Blocks

The full form of a for loop block is
for InitSimpleStatement; Condition; PostSimpleStatement {
	// do something
}

for is a keyword. The InitSimpleStatement and PostSimpleStatement portions must be both simple statements, and the PostSimpleStatement portion must not be a short variable declaration. Condition must be an expression which result is a boolean value. The three portions are all optional.

If the InitSimpleStatement in a for loop block is present, it will be executed only once before executing other statements in the for loop block.

The Condition expression will be evaluated at each loop step. If the evaluation result is false, then the loop will end. Otherwise the body of the loop will get executed.

The PostSimpleStatement will be executed at the end of each loop step.

Unlike many other programming languages, the three parts following the for keyword can't be enclosed in a pair of ().

A for loop example. The example will print the integers from 0 to 9.
for i := 0; i < 10; i++ {
	fmt.Println(i)
}

If the InitSimpleStatement and PostSimpleStatement portions are both absent (just view them as blank statements), their nearby two semicolons can be omitted. The form is called as condition-only for loop form. It is the same as the while loop in other languages.
var i = 0
for ; i < 10; {
	fmt.Println(i)
	i++
}
for i < 20 {
	fmt.Println(i)
	i++
}

If the Condition portion is absent, compilers will view it as true.
for i := 0; ; i++ { // same as: for i := 0; true; i++ {
	fmt.Println(i)
	if i >= 10 {
		break
	}
}

// Following foure endless loops are equivalent to each other.
for ; true; {
}
for true {
}
for ; ; {
}
for {
}

A for control flow forms two code blocks, one is implicit and one is explicit. The explicit one is nested in the implicit one. If the InitSimpleStatement in a for block is a short variable declaration, then the declared variables are only visible within the for block. For example, the following code snippet print 012 instead of 0.
for i := 0; i < 3; i++ {
	fmt.Print(i)
	i := i // The left i is a new declared variable,
	       // and the right i is the loop variable.
	i = 10 // The new delcared variable is modified,
	       // but the old one (the loop variable) is not.
}

Like many other popular languages, a break statement can be used to make execution jump out of the for loop control flow block in advance, if the for loop control flow block is the innermost breakable control flow block containing the break statement.
i := 0
for {
	if i >= 10 {
		break
	}
	i++
	fmt.Println(i)
}

Like many other popular languages, a continue statement can be used to end the current loop step in advance, if the for loop control flow block is the innermost loop control flow block containing the continue statement. For example, the following code snippet will print 13579.
for i := 0; i < 10; i++ {
	if i % 2 == 0 {
		continue
	}
	fmt.Print(i)
}

switch-case Control Flow Blocks

switch-case control flow block is another kind of conditional execution control flow block.

The full form a switch-case block is
switch InitSimpleStatement; CompareOperand0 {
case CompareOperandList1:
	// do something
case CompareOperandList2:
	// do something
...
case CompareOperandListN:
	// do something
default:
	// do something
}

switch, case and default are keywords. The InitSimpleStatement portion must be a simple statement. The CompareOperand0 portion is an expression which is viewed as a typed value (if it is an untyped literal, then it is viewed as a type value of its default type), hence it can't be an untyped nil. Each of the CompareOperandListX (X may represent from 1 to N) portions must be a comma separated expression list. Each of these expressions shall be comparable with CompareOperand0. If any of the expressions is an untyped value, then compilers will try to convert it to the type of CompareOperand0. If the conversion is impossible to achieve, compilation fails.

Each case CompareOperandListX: or default: opens (and is followed by) an implicit code block. The implicit code block and that case CompareOperandListX: or default: forms a branch. Each such branch is optional to be present. We call an implicit code block in such a branch as a branch code block later.

There can be at most one default branch in a switch-case control flow block.

Besides the branch code blocks, each switch-case control flow forms two code blocks, one is implicit and one is explicit. The explicit one is nested in the implicit one. All the branch code blocks are nested in the explicit one (and nested in the implicit one indirectly).

switch-case control flow blocks are breakable, so break statements can also be used in any branch code block in a switch-case control flow block to make execution jump out of the switch-case control flow block in advance.

When a switch-case control flow gets executed, the CompareOperand0 expression will be only evaluated once. The evaluation result is always a typed value. The evaluation result will be compared (by using the == operator) with the evaluation result of each expression in the CompareOperandListX expression lists, from top to down and from left to right. If an expression is found to be equal to CompareOperand0, the comparision process stops and the corresponding branch code block of the expression will be executed. If none expressions are found to be equal to CompareOperand0, the default branch code block (if it is present) will get executed.

A switch-case control flow example:
package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	rand.Seed(time.Now().UnixNano())
	switch n := rand.Intn(100); n%9 {
	case 0:
		fmt.Println(n, "is a multiple of 9.")

		// Different from many other languages, in Go,
		// the execution will automatically jumps out of the
		// switch-case block at the end of each branch block.
		// No "break" statement is needed here.
	case 1, 2, 3:
		fmt.Println(n, "mod 9 is 1, 2 or 3.")
		break // here, this "break" statement is nonsense.
	case 3, 4, 5, 6: // here, 3 is redundant.
		fmt.Println(n, "mod 9 is 4, 5 or 6.")
	default:
		fmt.Println(n, "mod 9 is 7 or 8.")
	}
}

The rand.Intn function returns a positive int random value which is smaller than the specified argument.

As the comments in the above example describes, unlike many other languages, in Go, at the end of each branch code block, the execution will automatically break out of the corresponding switch-case control block. Then how to let the execution slip into the next branch code block? Go provides a fallthrough keyword to do this task. For example, in the following example, every branch code block will get executed, by their orders, from top to down.
rand.Seed(time.Now().UnixNano())
switch n := rand.Intn(100) % 5; n {
case 0, 1, 2, 3, 4:
	fmt.Println("n =", n)
	fallthrough // make the execution slip into the next branch.
case 5, 6, 7, 8:
	n := 99 // a new declared variable also called "n",
	_ = n   // it is only visible in this branch code block.
	fallthrough
default:
	fmt.Println(n) // this "n" is always smaller than 5.
}

Please note, a fallthrough statement must be the final statement in a branch. And a fallthrough statement can't show up in the final branch in a switch-case control flow block. For example, the following fallthrough usages are all illegal.
switch n := rand.Intn(100) % 5; n {
case 0, 1, 2, 3, 4:
	fmt.Println("n =", n)
	if true {
		fallthrough // error: not the final statement in this branch.
	}
case 5, 6, 7, 8:
	n := 99
	fallthrough // error: not the final statement in this branch.
	_ = n
default:
	fmt.Println(n)
	fallthrough // error: shoud not show up in the final branch.
}

The InitSimpleStatement and CompareOperand0 portions in a switch-case control flow are both optional. If the CompareOperand0 portion is absent, it will be viewed as true, a typed value of the built-in type bool. If the InitSimpleStatement portion is absent, the semicolon following it can be omitted. And as above has mentioned, all branches are optional. So the following code blocks are all legal, all of them can be viewed as no-ops.
switch n := 5; n {
}

switch 5 {
}

switch n := 5; {
}

switch {
}

For the latter two switch-case control flow blocks in the last example, as above has mentioned, each of the absent CompareOperand0 portions is viewed as a typed value true of the built-in type bool. So the following code snippet will print hello.
switch {
case true: fmt.Println("hello")
default: fmt.Println("bye")
}

An obvious differences from many other languages is the order of the default branch in a switch-case control flow block can be arbitrary. For example, the following three switch-case control flow blocks are equivalent to each other.
switch n := rand.Intn(3); n {
case 0: fmt.Println("n == 0")
case 1: fmt.Println("n == 1")
default: fmt.Println("n == 2")
}

switch n := rand.Intn(3); n {
default: fmt.Println("n == 2")
case 0: fmt.Println("n == 0")
case 1: fmt.Println("n == 1")
}

switch n := rand.Intn(3); n {
case 0: fmt.Println("n == 0")
default: fmt.Println("n == 2")
case 1: fmt.Println("n == 1")
}

goto Statement And Label Declaration

Like many other languages, Go also supports goto statement. A goto keyword must be followed a label to form a statement. A label is declared with the form LabelName:, where LabelName must be an identifier. A label which name is not the blank identifier must be used at least once.

A goto statement will make the execution jump to the next statement following the declaration of the label used in the goto statement.

A label must be declared within a function body. A use of a label can appear before or after the declaration of the label. But a label is not visiable (and can't appear) outside the code block the label is declared in.

An example using goto statement and label:
package main

import "fmt"

func main() {
	i := 0
	
Next: // a label is declared.
	fmt.Println(i)
	i++
	if i < 5 {
		goto Next // execution jumps
	}
}

As above has mentioned, a label is not visiable (and can't appear) outside the code block the label is declared in. So the following example fails to compile.
package main

func main() {
goto Label1 // error
	{
		Label1:
		goto Label2 // error
	}
	{
		Label2:
	}
}

Another note, if a label is declared within the scope of a variable, then the uses of the label can't appear before the declaration of the variable. (Identifier scopes will be explained in the article blocks and scopes in Go later.)

The following example also fails to compile.
package main

import "fmt"

func main() {
	i := 0
Next:
	if i >= 5 {
		goto Exit // error: goto Exit jumps over declaration of k
	}
	
	k := i + i
	fmt.Println(k)
	i++
	goto Next
Exit: // this label is declared in the scope of k.
}

To make the above code compile okay, we must adjust the scope of the variable k. There are two ways to fix the problem in the last example.

One way is to shrink the scope of the variable k.
func main() func main() {
	i := 0
Next:
	if i >= 5 {
		goto Exit
	}
	// Create an explicit code block to shrink the scope of k.
	{
		k := i + i
		fmt.Println(k)
	}
	i++
	goto Next
Exit:
}
The other way is to enlarge the scope of the variable k.
func main() {
	var k int // move the declaration of k here.
	i := 0
Next:
	if i >= 5 {
		goto Exit
	}
	
	k = i + i
	fmt.Println(k)
	i++
	goto Next
Exit:
}

break And continue Statements With Labels

A goto statement must contain a label. A break or continue statement can also contain a label, but the label is optional.

If a break statement contain a label, the label must be declared just before a breakable control flow block which contains the break statement. We can view the label name as the name of the breakable control flow block. The break statement will make execution jump out of the breakable control flow block, even if the breakable control flow block is not the innermost breakable control flow block containing break statement.

If a continue statement contain a label, the label must be declared just before a loop control flow block which contains the continue statement. We can view the label name as the name of the loop control flow block. The continue statement will end the current loop step of the loop control flow block in advance, even if the loop control flow block is not the innermost loop control flow block containing the continue statement.

The following is an example of using break and continue statements with labels.
package main

import "fmt"

func FindSmallestPrimeLargerThan(n int) int {
Outer:
	for n++; ; n++{
		for i := 2; ; i++ {
			switch {
			case i * i > n:
				break Outer
			case n % i == 0:
				continue Outer
			}
		}
	}
	return n
}

func main() {
	for i := 90; i < 100; i++ {
		n := FindSmallestPrimeLargerThan(i)
		fmt.Println("The smallest prime number larger than", i, "is", n)
	}
}


The Go 101 project is hosted on both github and gitlab. Welcome to improve Go 101 articles by submitting corrections for all kinds of mistakes, such as typos, grammar errors, wording inaccuracies, description flaws, code bugs and broken links.

Support Go 101 by playing Tapir's games. Cryptocurrency donations are also welcome:
Bitcoin: 1xucQbv5jujFPPwhyg395ri5yV71hx9g9
Ethereum: 0x5dc4aa2c2bbfaadae373dadcfca11b3358912212