If and switch in Go

Programs need to make decisions: do this if the file exists, do that if the number is negative. In Go you make decisions with if and switch. By the end of this lesson you’ll know both, including the Go-specific bits: the short statement before a condition and a switch that doesn’t fall through.

if and else

An if statement runs a block of code only when its condition is true. An optional else block runs when it’s false:

package main

import "fmt"

func main() {
	temperature := 31

	if temperature > 25 {
		fmt.Println("It's warm, wear shorts.")
	} else {
		fmt.Println("Bring a jacket.")
	}
}Code language: Go (go)
It's warm, wear shorts.Code language: plaintext (plaintext)

Two things stand out if you know C, Java or JavaScript. There are no parentheses around the condition (gofmt removes them if you add them), and the curly braces are always required, even for a single line. The opening brace must also be on the same line as the if, and else goes on the same line as the closing brace before it.

To check several conditions in a row, chain them with else if. Go tests them from top to bottom and runs the first block whose condition is true:

package main

import "fmt"

func main() {
	score := 72

	if score >= 90 {
		fmt.Println("A")
	} else if score >= 80 {
		fmt.Println("B")
	} else if score >= 70 {
		fmt.Println("C")
	} else {
		fmt.Println("Keep practicing")
	}
}Code language: Go (go)
CCode language: plaintext (plaintext)

Comparison and logical operators

A condition is any expression of type bool. You usually build one with comparison operators, and combine several with logical operators:

OperatorMeaningExample
==equalname == "Erik"
!=not equaln != 0
<, <=less than, less than or equalage < 18
>, >=greater than, greater than or equalscore >= 90
&&and: both must be trueage >= 18 && hasTicket
||or: at least one must be trueday == "Sat" || day == "Sun"
!not: flips true and false!done
package main

import "fmt"

func main() {
	age := 20
	hasTicket := true
	vip := false

	if age >= 18 && hasTicket {
		fmt.Println("Welcome to the concert.")
	}
	if vip || age > 65 {
		fmt.Println("Free drink!")
	}
	if !vip {
		fmt.Println("Regular entrance, please.")
	}
	fmt.Println("go" < "golang", 3 == 3.0)
}Code language: Go (go)
Welcome to the concert.
Regular entrance, please.
true trueCode language: plaintext (plaintext)

Strings compare byte by byte, which for plain English text is alphabetical order (but uppercase letters sort before lowercase ones). You can only compare values of the same type: comparing an int variable with a float64 variable is a compile error. 3 == 3.0 works only because both are untyped constants.

&& and || short-circuit: Go evaluates the right side only when it needs to. In a && b, if a is false, b is never evaluated. That lets you write guards like len(s) > 0 && s[0] == '#' without crashing on an empty string.

Conditions must be booleans

Some languages treat 0, empty strings or null as false. Go doesn’t. A condition must be a real bool, so you write the comparison out:

package main

import "fmt"

func main() {
	count := 1
	if count {
		fmt.Println("we have some")
	}
}Code language: Go (go)
./main.go:7:5: non-boolean condition in if statementCode language: plaintext (plaintext)

The fix is if count > 0. It’s a little more typing, and it removes a whole class of bugs.

if with a short statement

An if can start with a short statement, separated from the condition by a semicolon. It runs first, and the variables it declares exist only inside the if and its else branches. You’ll see this constantly with functions that return a value and an error, like strconv.Atoi from the strings lesson:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	if n, err := strconv.Atoi("42"); err != nil {
		fmt.Println("not a number:", err)
	} else if n%2 == 0 {
		fmt.Println(n, "is even")
	} else {
		fmt.Println(n, "is odd")
	}
}Code language: Go (go)
42 is evenCode language: plaintext (plaintext)

The % operator gives the remainder of a division, so n%2 == 0 means “n is even”. Both n and err are visible in every branch. After the closing brace they’re gone:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	if n, err := strconv.Atoi("42"); err == nil {
		fmt.Println(n)
	}
	fmt.Println(n)
}Code language: Go (go)
./main.go:12:14: undefined: nCode language: plaintext (plaintext)

That’s the point of the short statement: it keeps a variable that’s only needed for one decision from leaking into the rest of the function. If you need the value afterwards, declare it before the if instead.

switch

When you compare one value against a list of possibilities, a switch reads better than a long else if chain. Go evaluates the expression after switch once, then compares it with each case from top to bottom and runs the first match. The optional default runs when nothing matches:

package main

import "fmt"

func main() {
	command := "stop"

	switch command {
	case "start":
		fmt.Println("Starting...")
	case "stop":
		fmt.Println("Stopping...")
	case "status":
		fmt.Println("All good.")
	default:
		fmt.Println("Unknown command:", command)
	}
}Code language: Go (go)
Stopping...Code language: plaintext (plaintext)

Several values in one case

A case can list several values, separated by commas. The case matches if any of them is equal:

package main

import "fmt"

func main() {
	day := "Sun"

	switch day {
	case "Sat", "Sun":
		fmt.Println("Weekend!")
	case "Mon", "Tue", "Wed", "Thu", "Fri":
		fmt.Println("Workday.")
	default:
		fmt.Println("That's not a day.")
	}
}Code language: Go (go)
Weekend!Code language: plaintext (plaintext)

The compiler checks constant cases for you. List the same value twice and you get an error like duplicate case "Sun" (constant of type string) in expression switch.

switch with a short statement

Just like if, a switch can start with a short statement whose variables live only inside the switch:

package main

import (
	"fmt"
	"strings"
)

func main() {
	switch ext := strings.ToLower(".PNG"); ext {
	case ".jpg", ".jpeg", ".png", ".gif":
		fmt.Println(ext, "is an image")
	case ".go":
		fmt.Println(ext, "is Go source")
	default:
		fmt.Println(ext, "is something else")
	}
}Code language: Go (go)
.png is an imageCode language: plaintext (plaintext)

The tagless switch

Leave out the expression after switch, and each case becomes a full condition. The first case that is true wins. This is Go’s tidy way of writing an if/else if chain, and it’s very common:

package main

import "fmt"

func main() {
	hour := 15

	switch {
	case hour < 12:
		fmt.Println("Good morning")
	case hour < 18:
		fmt.Println("Good afternoon")
	default:
		fmt.Println("Good evening")
	}
}Code language: Go (go)
Good afternoonCode language: plaintext (plaintext)

Order matters here. hour < 18 is also true for 9 in the morning, but the first case catches that earlier. I use a tagless switch as soon as an if chain gets a third branch.

No fallthrough by default

In C and Java, a case keeps running into the next case unless you write break. Forgetting that break is a classic bug. Go turns it around: each case ends on its own, so you never need break in a switch. When you really want to continue into the next case, say so with the fallthrough keyword:

package main

import "fmt"

func main() {
	level := 2

	switch level {
	case 1:
		fmt.Println("level 1 setup")
	case 2:
		fmt.Println("level 2 setup")
		fallthrough
	case 3:
		fmt.Println("level 3 setup")
	case 4:
		fmt.Println("level 4 setup")
	}
}Code language: Go (go)
level 2 setup
level 3 setupCode language: plaintext (plaintext)

fallthrough jumps straight into the next case’s body without checking its condition, and it only goes one step: case 4 doesn’t run. It must be the last statement in a case. In practice you’ll rarely need it; listing several values in one case covers most situations more clearly.

There’s one more kind of switch, the type switch, which checks what type a value has. It only makes sense once you know interfaces, so it’s covered in the interfaces lesson.

Practice

1. Leap year. A year is a leap year if it’s divisible by 4, except years divisible by 100, unless they’re also divisible by 400. Store a year in a variable and print whether it’s a leap year, using one if with && and ||. Try 2024, 1900 and 2000.

Show a solution
package main

import "fmt"

func main() {
	year := 1900

	if year%4 == 0 && (year%100 != 0 || year%400 == 0) {
		fmt.Println(year, "is a leap year")
	} else {
		fmt.Println(year, "is not a leap year")
	}
}Code language: Go (go)
1900 is not a leap yearCode language: plaintext (plaintext)

2. Parse an answer. Take a string such as " Yes ". Clean it up with strings.TrimSpace and strings.ToLower in a switch short statement, then print confirmed for “y” or “yes”, cancelled for “n” or “no”, and please answer yes or no for anything else.

Show a solution
package main

import (
	"fmt"
	"strings"
)

func main() {
	input := " Yes "

	switch answer := strings.ToLower(strings.TrimSpace(input)); answer {
	case "y", "yes":
		fmt.Println("confirmed")
	case "n", "no":
		fmt.Println("cancelled")
	default:
		fmt.Println("please answer yes or no")
	}
}Code language: Go (go)
confirmedCode language: plaintext (plaintext)

Key takeaways

  • if needs no parentheses but always needs braces, and its condition must be a bool.
  • && and || short-circuit, so the right side runs only when needed.
  • if v, err := f(); err != nil { ... } declares variables that exist only inside that if/else.
  • A switch can match values, several values per case, or (tagless) arbitrary conditions. The first match wins.
  • Cases don’t fall through and need no break. Use fallthrough only when you really mean it.