Control Flow and Booleans
A program consists of statements. The statements you’ve written so far have been either expressions or declarations. In this chapter, you’ll learn about control flow statements and how they influence the execution of your code.
Control flow statements
When a program runs, a control process executes its statements in order, one after the next, until the program reaches the end. Control flow statements influence this process and can cause it to skip or repeat statements.
This chapter covers the if
and while
statements.
if
Suppose your code has a variable result
that stores a student’s test result:
var result = 12
You can use an if
statement to inform the student of their result:
if result >= 10 {
print("You passed.")
}
An if
statement consists of the keyword if
, a condition, and a body, which is a list of statements surrounded by braces. You format an if
statement as follows:
- Put the opening brace of the body on the same line as the condition, preceded by a space.
- Indent the body to set it apart from the rest of the code.
- Put the closing brace of the body on a new line, at the same indentation level as the
if
keyword.
This formatting isn’t part of the syntax — your code will work without it — but it’s an important convention that brings uniformity to your code when working with other programmers.
else
The if
statement is a branch statement. Branch statements create different paths (branches) through your code. The previous example has two paths:
- If the condition
result >= 10
is met, the control process executes the body of theif
statement and then continues with the statement that follows the closing brace. - If the condition is not met, the control process skips the body of the
if
statement and goes directly to the statement that follows the closing brace.
You can change what happens on the second path by adding an else
clause to the if
statement. This clause contains statements that execute when the condition is not met:
result = 9
if result >= 10 {
print("You passed.")
} else {
print("You failed.")
}
Here, the control process skips the first path and executes the body of the else
clause instead.
You can replace the body of an else
clause with a new if
statement. This results in a branch that can have any number of paths:
result = 14
if result < 10 {
print("You failed.")
} else if result < 12 {
print("You were cutting it close.")
} else if result >= 18 {
print("You passed with flying colors.")
} else {
print("You passed.")
}
No matter the number of paths you have, an if
statement only executes the first path whose condition is met; the rest are ignored.
while
The while
statement is a loop statement. It consists of the keyword while
, a condition, and a body. The following example uses a while
loop to print a countdown from 5 to 0:
var count = 5
while count >= 0 {
print(count)
count -= 1
}
Here’s how this works:
- When the control process encounters the
while
statement, it first checks the condition. - The condition is met, so the control process executes the body, printing the number 5.
- The control process returns to the condition and checks it again.
- The condition is still met (
count
is now 4), so the control process executes the body, printing the number 4. - This repeats four more times. The final iteration of the loop prints the number 0, then decrements
count
to -1. This causes the subsequent condition check to fail. The control process then skips the body and continues past the closing brace.
Notice how the value of count
decreases with every iteration of the loop and eventually causes the loop to stop. The body of a while
loop must affect the loop’s condition in some way; otherwise the loop would run forever, and your program would never end.
Comparison operators
The if
and while
statements both include a condition. You can use the following comparison operators to build these conditions:
- Less than:
<
- Greater than:
>
- Equal to:
==
- Not equal to:
!=
- Less than or equal to:
<=
- Greater than or equal to:
>=
Pay particular attention to the equality operator (==
). You use a double equals sign to test if two values are equal. A single equals sign assigns a value to a constant or variable.
These comparison operators aren’t exclusive to numbers. You also use them to compare strings:
let name = "Alice"
name == "Alice"
name < "Bob"
In this case, the operators compare the strings alphabetically.
Booleans
The result of a comparison is a truth value, which is either true
or false
. The type of these values is Bool
.
Note
Type Bool
is named in honor of George Boole, who devised an entire algebra to work with truth values. This Boolean algebra is particularly important in computer science because of the similarities between Boolean values (true
and false
) and bits (1 and 0).
You can declare constants and variables of type Bool
and assign them either a Boolean literal (true
or false
) or an expression that results in a Boolean:
let swiftIsCool = true
let studentHasPassingScore = result >= 10
Control flow statements can take any expression of type Bool
as a condition, not just comparisons:
if studentHasPassingScore {
print("You passed.")
}
This statement executes its body only if studentHasPassingScore
is true
.
Boolean operators
Swift includes the three primary operators from Boolean algebra:
- AND (
&&
) is a binary operator that returnstrue
if both of its operands aretrue
. - OR (
||
) is a binary operator that returnstrue
if at least one of its operands istrue
. - NOT (
!
) is a unary operator that inverts its operand:true
becomesfalse
andfalse
becomestrue
. This is a prefix operator, so you write it before its operand.
The following truth tables show the result of each operator for all possible operands a
and b
:
a | b | a && b |
---|---|---|
true | true | true |
true | false | false |
false | true | false |
false | false | false |
a | b | a || b |
---|---|---|
true | true | true |
true | false | true |
false | true | true |
false | false | false |
a | !a |
---|---|
true | false |
false | true |
You can use these operators to build complex Boolean expressions:
if !studentHasPassingScore {
print("You failed.")
}
let year = 2020
if year.isMultiple(of: 4) && !year.isMultiple(of: 100) ||
year.isMultiple(of: 400) {
print("\(year) is a leap year.")
}
Of the Boolean operators, !
has the highest precedence, followed by &&
, and finally ||
. When in doubt, use parentheses to make your code easier to understand:
if (year.isMultiple(of: 4) && !year.isMultiple(of: 100)) ||
year.isMultiple(of: 400) {
print("\(year) is a leap year.")
}
This example is equivalent to the previous one, but the parentheses make the precedence explicit.
Blocks
The body of an if
or while
statement consists of a list of statements surrounded by braces. This is known as a block. Blocks introduce important concepts such as nesting, scope, and visibility that you’ll learn about next.
Nested control flow
Blocks can contain any statement, including control flow statements. This can result in nested control flow statements.
The following example prints a countdown from 10 to 1, where the numbers 3, 2, and 1 are replaced with dots. It does this by nesting an if
statement inside the body of a while
statement:
count = 10
while count > 0 {
if count == 3 {
print("...")
} else if count == 2 {
print("..")
} else if count == 1 {
print(".")
} else {
print(count)
}
count -= 1
}
print("Action!")
It’s essential that you get comfortable with nested control flow; you’ll need it to solve many complex problems.
Local and global scope
When you declare a constant or variable inside of a block, its scope is limited to that block. When the block ends, the constant or variable is removed from memory.
Here’s an alternative implementation of the countdown example:
count = 10
while count > 0 {
if count > 3 {
print(count)
} else {
var dots = count
while dots > 0 {
print(".", terminator: "")
dots -= 1
}
print()
}
count -= 1
}
print("Action!")
Note
By setting the terminator
parameter of the print
function to an empty string, you stop it from printing a newline character after every string. The final print()
statement completes the line by only printing a newline terminator.
In this code, the scope of variable dots
is limited to the block of the else
clause, including any nested blocks. The variable is initialized at the start of the block, and removed when the block ends. This happens multiple times because of the surrounding while
loop.
Constants and variables with limited scope are known as local constants and variables. Those that are declared outside of a block (such as count
) are known as global constants and variables and stay in memory for the entire duration of your program.
Visibility and shadowing
The concept of visibility is closely related to scope. Consider the following example, which prints the squares of the numbers 1 through 20:
var number = 1
while number <= 20 {
let result = number * number
print(result)
number += 1
}
Now consider what happens when you put this code in between some earlier examples from this chapter:
var result = 12
if result >= 10 {
print("You passed.")
}
var number = 1
while number <= 20 {
let result = number * number
print(result)
number += 1
}
if result >= 10 {
print("You passed.")
} else {
print("You failed.")
}
You now have both a global variable and a local constant named result
. This is allowed and works as expected.
In the body of the while
loop, result
refers to the local constant. Its presence shadows the global variable of the same name. That global variable still exists, but it’s temporarily invisible. When the local constant goes out of scope, the global variable becomes visible again.
Computational thinking
Now that you’ve learned about control flow statements, you’re ready to take on bigger challenges than the exercises you’ve solved so far.
As the exercises increase in difficulty, you’ll find that the hardest part of programming is translating the ideas in your head to executable programs. This is known as computational thinking.
Computational thinking isn’t something you can learn overnight. It requires many hours of practice to master. Take your time with the exercises in this course, and if you struggle, use the following step-by-step approach:
- Think before you start. Don’t write any code until you understand both the problem at hand and how to solve it. Pencil and paper are your friends here.
- Write down a step-by-step guide on how to solve the problem. This guide is known as an algorithm.
- Convert your algorithm into pseudocode. Pseudocode is an intermediate step between English and valid code. You can make up your own pseudo-language, or use Swift, but don’t worry about writing valid code just yet. Instead, focus on finding the branches and loops in your algorithm.
- Verify the correctness of your algorithm by executing it yourself, keeping track of your variables on a piece of paper. Make sure your algorithm handles all input values, even edge cases.
- Finally, convert your pseudocode into valid Swift code.
Although you can easily solve small exercises without this approach, you’ll soon find out that jumping straight into code is not an option for complex problems.
Don’t be fooled by what you see in movies and television. Programmers spend most of their time thinking about code, not writing it. Practice often, and in time, computational thinking will become second nature.
Up next
The upcoming exercises will test your understanding of control flow statements and give you a chance to practice computational thinking. Work your way through the exercises. When you’re done, continue to the next chapter, where you’ll learn more about control flow statements.