Conditionals
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
ifand{with no parentheses. - The braces are required, even for a single line.
elsemust 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.
| Operator | Question 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.!bannedis 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 anif. - Putting
elseon its own line instead of after}. - Using a number or a string as the condition. It must be a bool.
- Comparing an
intwith afloat64. Convert one side first. - Comparing decimals with
==and being surprised by0.30000000000000004. - Mixing
&&and||without parentheses and getting a different answer than you meant. - Asking a risky question before the safe one: put
len(word) > 0beforeword[0]. - Expecting a
switchcase to run the next case too. Go stops after the match. - Leaving a function without a
returnon every path. The error ismissing 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:
- Set
battery := 20and run theif / else if / elseprogram from this lesson. Then try21,80and81, and notice which side of each border changes the answer. - Print
7 == 7,7 != 7,"go" == "Go"and"apple" < "banana"and read the four answers. - Write
score := 10andif score = 10 {, run it, and read the error. Then fix it with==. - Make
age := 25andhasTicket := falseand printage >= 18 && hasTicket, thenage >= 18 || hasTicket, then!hasTicket. - Print
len(word) > 0 && word[0] == 'a'withword := ""and again withword := "apple". - Write a
switchonmonth := 4with a case for12, 1, 2that printswinter, a case for3, 4, 5that printsspring, and adefault. - Copy the
shippingfunction, setweightto0,2and30, and run it each time. Then remove the finalreturn "parcel"and read themissing returnerror.