Skip to content

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:

swift
var result = 12

You can use an if statement to inform the student of their result:

swift
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:

  1. If the condition result >= 10 is met, the control process executes the body of the if statement and then continues with the statement that follows the closing brace.
  2. 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:

swift
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:

swift
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:

swift
var count = 5
while count >= 0 {
  print(count)
  count -= 1
}

Here’s how this works:

  1. When the control process encounters the while statement, it first checks the condition.
  2. The condition is met, so the control process executes the body, printing the number 5.
  3. The control process returns to the condition and checks it again.
  4. The condition is still met (count is now 4), so the control process executes the body, printing the number 4.
  5. 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:

swift
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:

swift
let swiftIsCool = true
let studentHasPassingScore = result >= 10

Control flow statements can take any expression of type Bool as a condition, not just comparisons:

swift
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 returns true if both of its operands are true.
  • OR (||) is a binary operator that returns true if at least one of its operands is true.
  • NOT (!) is a unary operator that inverts its operand: true becomes false and false becomes true. 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:

aba && b
truetruetrue
truefalsefalse
falsetruefalse
falsefalsefalse
aba || b
truetruetrue
truefalsetrue
falsetruetrue
falsefalsefalse
a!a
truefalse
falsetrue

You can use these operators to build complex Boolean expressions:

swift
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:

swift
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:

swift
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:

swift
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:

swift
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:

swift
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:

  1. 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.
  2. Write down a step-by-step guide on how to solve the problem. This guide is known as an algorithm.
  3. 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.
  4. 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.
  5. 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.