Course outline
Course / The terminal and the language

Conditionals

Reading

The idea

So far your programs ran every line, top to bottom, every time. Real programs have to make choices: show a warning only when the disk is full, charge a different price for children, stop early when the input is bad. In Go, a choice is a question with a yes-or-no answer, and the answer decides which lines run.

Think of a railway. The train is your program, and a switch is a place in the track where it can go one way or another. The yes-or-no answer is the lever. Snippets that start with package main are full programs. The others go inside func main().

If and else

An if is a switch with two tracks: when the answer is yes, the train takes the track inside the braces, and when it is no, it skips that track.

package main

import "fmt"

func main() {
	battery := 85
	if battery > 80 {
		fmt.Println("High: unplug soon")
	} else if battery > 20 {
		fmt.Println("Fine: carry on")
	} else {
		fmt.Println("Low: find a charger")
	}
	fmt.Println("Done")
}
High: unplug soon
Done

Go checks the questions from the top. The first one that is yes wins, its track runs, and every other track is skipped. else if adds another question, and a final else catches everything left over. Whichever track runs, the train rejoins afterward, so Done always prints.

A few rules worth knowing:

  • The question goes between if and { with no parentheses.
  • The braces are required, even for a single line.
  • else must sit on the same line as the closing } before it.

The question must be a real yes-or-no value, which Go calls a bool. A number is not accepted:

count := 5
if count {
	fmt.Println("has items")
}
./main.go:7:5: non-boolean condition in if statement

The message says that the condition on line 7, column 5 is not a bool. You have to ask a real question, like count > 0, which the next section covers. Putting else on its own line also fails:

./main.go:10:2: syntax error: unexpected keyword else, expected }

Go ends the if at the closing brace when a line break follows it, so the else on the next line has nothing to belong to. Keep it as } else {.

Comparison operators

A comparison asks a question about two values, like holding two objects up to a weighing scale. The answer is always true or false.

OperatorQuestion it asks
==are they equal?
!=are they different?
<is the left one smaller?
<=is the left one smaller or equal?
>is the left one bigger?
>=is the left one bigger or equal?
a, b := 7, 7
fmt.Println(a == b, a != b, a < b, a <= b, a > b, a >= b)
fmt.Println("apple" < "banana", "Zebra" < "apple", "go" == "Go")
true false false true false true
true true false

The first line compares two equal numbers, so ==, <= and >= say yes. Strings can be compared too. == needs an exact match, so "go" and "Go" are different. < puts strings in dictionary order by their bytes, which means every capital letter comes before every lower-case letter. That is why "Zebra" < "apple" is true.

The two most common slips have their own errors. = stores a value and == asks a question, and mixing them up is rejected:

score := 10
if score = 10 {
	fmt.Println("ten")
}
./main.go:7:11: syntax error: cannot use assignment score = 10 as value

The message says you wrote an assignment where a yes-or-no value belongs. Change it to score == 10. The other slip is comparing values of different types, which Go will not mix, just like in the type conversion lesson:

items := 3
price := 2.5
fmt.Println(items == price)
./main.go:8:23: invalid operation: items == price (mismatched types int and float64)

Convert one side first, for example float64(items) == price. One more warning: decimal numbers are stored with tiny rounding errors, so comparing them for exact equality can surprise you.

x := 0.1
y := 0.2
fmt.Println(x+y == 0.3, x+y)
false 0.30000000000000004

When a decimal result matters, compare with < or >, or work in whole numbers such as cents.

Boolean logic

Sometimes one question is not enough. Picture a club with a bouncer at the door. The bouncer combines yes-or-no answers using three rules:

  • && means and: you get in only if you have an ID and a ticket.
  • || means or: you get in if you are on the guest list or you know the owner.
  • ! means not: it flips the answer. !banned is true when you are not banned.
age := 25
hasTicket := true
fmt.Println(age >= 18 && hasTicket)
fmt.Println(age >= 18 && !hasTicket)
isWeekend := false
isHoliday := true
fmt.Println(isWeekend || isHoliday)
fmt.Println(!isWeekend)
true
false
true
true

The first line is true because both halves are true. The second is false because !hasTicket is false. The third is true because one half is enough for ||. The fourth flips false into true.

&& binds tighter than ||, the same way multiplication comes before addition. When you mix them, add parentheses so the meaning is clear to the next reader:

fmt.Println(true || false && false)
fmt.Println((true || false) && false)
true
false

The first line reads as true || (false && false). The second line forces the or to be worked out first.

Go also stops early. For &&, if the left side is already false, the right side is never checked, because the answer cannot change. This is how you guard a risky check:

word := ""
fmt.Println(len(word) > 0 && word[0] == 'a')
word = "apple"
fmt.Println(len(word) > 0 && word[0] == 'a')
false
true

With the empty string, len(word) > 0 is false, so word[0] is never touched. Without the guard, asking for position 0 of an empty string would stop your program. || works the other way: when the left side is already true, the right side is skipped.

Switch

When one value decides between many tracks, a chain of else if gets long. A switch is a big railway junction: one lever and many tracks, each marked with the values it accepts.

month := 7
switch month {
case 12, 1, 2:
	fmt.Println("winter")
case 3, 4, 5:
	fmt.Println("spring")
case 6, 7, 8:
	fmt.Println("summer")
case 9, 10, 11:
	fmt.Println("autumn")
default:
	fmt.Println("not a month")
}
summer

Go compares month with each case from the top and runs the first one that matches. A case can list several values separated by commas. default catches everything that matched nothing. Unlike some other languages, Go runs only the matching case and then leaves the switch, so you never need to write a break. A switch works on text too:

status := "shipped"
switch status {
case "new":
	fmt.Println("just arrived")
case "shipped":
	fmt.Println("on its way")
default:
	fmt.Println("unknown")
}
on its way

A switch with nothing after the word switch lets each case hold its own yes-or-no question, which makes it a tidy replacement for a long else if chain:

score := 72
switch {
case score >= 90:
	fmt.Println("A")
case score >= 70:
	fmt.Println("C or better")
default:
	fmt.Println("keep practicing")
}
C or better

Cases are checked from the top and the first yes wins, so put the strictest question first. Here a score of 95 matches the first case and never reaches the second.

Early returns

Imagine a hallway with exits along it. Once you know where you are going, you take the nearest exit instead of walking to the end. In code, return is that exit: it leaves the current function immediately.

A function is a named block of steps. The next lesson covers functions properly. For now, all you need is the shape the labs use: empty parentheses after the name, the type of the answer after them, and return to hand the answer back. The variable sits outside the function so the function can read it, like weight below.

package main

import "fmt"

var weight int = 8

func shipping() string {
	if weight <= 0 {
		return "invalid"
	}
	if weight < 2 {
		return "letter"
	}
	if weight >= 30 {
		return "freight"
	}
	return "parcel"
}

func main() {
	fmt.Println(shipping())
	weight = 40
	fmt.Println(shipping())
	weight = 1
	fmt.Println(shipping())
}
parcel
freight
letter

shipping handles the special cases first. Each if that matches returns right away, and the remaining lines never run. Whatever is left at the bottom must be the ordinary case, parcel. Notice that nothing needs an else, because a return already ends the function.

The same idea keeps code flat. Without early returns, each case would have to sit inside an else of the one before it, and the function would drift to the right with every new rule. The version above reads straight down instead. A return also works in main, where it ends the program on the spot:

stock := 0
if stock == 0 {
	fmt.Println("sold out")
	return
}
fmt.Println("add to cart")
sold out

Checking the bad or special cases first and leaving early is called a guard. It keeps the main path of the function at the left edge, where it is easy to read. Go also insists that a function with an answer type ends in a return on every path. Here weight is the variable declared outside the function, as above:

func size() string {
	if weight > 10 {
		return "heavy"
	}
}
./main.go:19:1: missing return

The function promises a string, but when weight is 10 or less it reaches the closing brace without returning anything. Line 19, column 1 is that closing brace. A final return with the other answer is what is missing.

Common mistakes

  • Writing = when you mean ==. A single = stores a value, and Go rejects it inside an if.
  • Putting else on its own line instead of after }.
  • Using a number or a string as the condition. It must be a bool.
  • Comparing an int with a float64. Convert one side first.
  • Comparing decimals with == and being surprised by 0.30000000000000004.
  • Mixing && and || without parentheses and getting a different answer than you meant.
  • Asking a risky question before the safe one: put len(word) > 0 before word[0].
  • Expecting a switch case to run the next case too. Go stops after the match.
  • Leaving a function without a return on every path. The error is missing return.
  • Putting a broad question before a narrow one in a chain, so the narrow one never runs.

Try it

The labs read their inputs from variables that sit outside the functions, and the tests change those variables to check your boundaries. Try these in your own main.go first:

  1. Set battery := 20 and run the if / else if / else program from this lesson. Then try 21, 80 and 81, and notice which side of each border changes the answer.
  2. Print 7 == 7, 7 != 7, "go" == "Go" and "apple" < "banana" and read the four answers.
  3. Write score := 10 and if score = 10 {, run it, and read the error. Then fix it with ==.
  4. Make age := 25 and hasTicket := false and print age >= 18 && hasTicket, then age >= 18 || hasTicket, then !hasTicket.
  5. Print len(word) > 0 && word[0] == 'a' with word := "" and again with word := "apple".
  6. Write a switch on month := 4 with a case for 12, 1, 2 that prints winter, a case for 3, 4, 5 that prints spring, and a default.
  7. Copy the shipping function, set weight to 0, 2 and 30, and run it each time. Then remove the final return "parcel" and read the missing return error.

Something wrong with this lesson? Report a problem