Skip to content
Theory · Operators · lesson 6 of 7

Ranges and the ternary operator

A museum sells a child ticket to kids aged 3 to 12. A twelve-year-old gets one: the boundary belongs to the stretch. A shop is open from 9:00 to 18:00, but at 18:00 the doors are already shut: the second boundary is the moment the stretch ends.

Swift has an operator for each case. Both create a range: a stretch of numbers from a lower bound to an upper bound.

The closed range ...

a...b includes both bounds, like the child ticket:

SWIFT
let childAges = 3...12
print(childAges.contains(12)) // true
print(childAges.contains(13)) // false

Let's go line by line:

  1. 3...12 is every whole number from 3 to 12, 12 included.
  2. contains checks whether a number falls in the range. The range itself knows how to do this, and you call it with a dot: childAges.contains(12) gives true.
  3. The number 13 is not in the range, and contains(13) gives false.

The lower bound cannot be larger than the upper one. 12...3 does not give an empty range; it crashes the program:

TEXT
Fatal error: Range requires lowerBound <= upperBound

The half-open range ..<

a..<b stops before b, like the shop's opening hours:

SWIFT
let openHours = 9..<18
print(openHours.contains(9)) // true
print(openHours.contains(18)) // false

The lower bound 9 is in the range, and the upper bound 18 is not. Here the clock picture is inaccurate: 9..<18 holds only the whole numbers 9 to 17, while real time also has minutes.

A half-open range is handy when the upper bound means an end or a count: 0..<5 holds exactly five numbers, from 0 to 4.

Interviewers for junior roles often ask for the difference between ... and ..<. The short answer: the first includes the upper bound, the second stops before it.

Parentheses around a range

To call contains on a range written out in place, put it in parentheses: (1...5).contains(5). Without them Swift applies .contains to the number 5 alone:

TEXT
error: value of type 'Int' has no member 'contains'

The ternary operator

You often need to pick one of two values: a child ticket or an adult one. That is what the ternary operator is for: condition ? a : b. Its name means "made of three parts": a condition and two values.

SWIFT
let age = 7
let ticket = childAges.contains(age) ? "child" : "adult"
print(ticket) // child

How Swift picks the value:

  1. First it evaluates the condition before ?: childAges.contains(age) is true.
  2. The condition is true, so the result is the value between ? and :, the string "child".
  3. Had the condition been false, the result would have been the value after :, the string "adult".

The ternary operator has two rules:

  • the condition before ? has type Bool;
  • both values have the same type, or the program does not compile.

If you mix a string and a number, as in age > 5 ? "big" : 0, the compiler answers like this:

TEXT
error: result values in '? :' expression have mismatching types 'String' and 'Int'

Common mistake

The shop's opening hours are written as a closed range:

SWIFT
let hours = 9...18 // ✗
print(hours.contains(18)) // true

The program thinks the shop is open at 18:00, when it is already closed. There is no error at compile time or at run time, only a wrong answer. When the upper bound means an end, you need ..<, as in openHours above.

Key points

  • a...b includes both bounds: (3...12).contains(12) is true.
  • a..<b stops before b: (9..<18).contains(18) is false.
  • contains checks whether a number falls in a range, and a range written out before .contains goes in parentheses.
  • condition ? a : b gives a if the condition is true and b if not; both values have the same type.