Swift Ranges

Ranges express a span of values between a lower and upper bound and power everything from loops to array slicing.

Closed Ranges with ...

The closed range operator (a...b) produces a range that runs from a to b inclusive, meaning both endpoints are included in the sequence. It requires that a be less than or equal to b, otherwise Swift raises a runtime error. Closed ranges are the natural choice whenever the upper bound itself is a value you want to include, such as looping from 1 to 10.

Looping with a closed range

for day in 1...7 {
    print("Day \(day) of the week")
}
// Prints Day 1 ... Day 7, seven lines total

Half-Open Ranges with ..<

The half-open range operator (a..<b) runs from a up to, but not including, b. This is especially useful when working with array indices, since a collection's valid indices run from 0 up to (but excluding) its count — writing 0..<array.count avoids ever indexing one past the end.

Half-open range over an array's indices

let colors = ["red", "green", "blue"]

for index in 0..<colors.count {
    print("\(index): \(colors[index])")
}
// 0: red
// 1: green
// 2: blue

One-Sided Ranges

Swift also supports one-sided ranges, where you omit one end of the range and let Swift infer it from context. array[2...] means from index 2 to the end, while array[..<2] means everything before index 2. One-sided ranges cannot be iterated directly with for-in because they have no defined starting or ending point on their own, but they work perfectly for subscripting collections.

Slicing an array with one-sided ranges

let letters = ["a", "b", "c", "d", "e"]

let fromThird = letters[2...]   // ["c", "d", "e"]
let firstTwo = letters[..<2]    // ["a", "b"]

print(fromThird)
print(firstTwo)

Ranges as Values

Ranges are not just loop syntax — they are first-class values of type ClosedRange<Int> or Range<Int> (or generic over any Comparable type). You can store a range in a constant, pass it to a function, and query it with contains(_:), which is handy for validating whether a number falls within acceptable bounds.

  • 1...5 is a ClosedRange<Int> including both 1 and 5
  • 1..<5 is a Range<Int> including 1 but excluding 5
  • contains(_:) checks membership without looping manually
  • Ranges work over any Comparable type, including Character and Double
  • reversed() lets you iterate a range from high to low

Storing and querying a range

let validAges = 18...65

func checkEligibility(age: Int) -> String {
    return validAges.contains(age) ? "Eligible" : "Not eligible"
}

print(checkEligibility(age: 30)) // Eligible
print(checkEligibility(age: 12)) // Not eligible

for n in (1...5).reversed() {
    print(n) // 5, 4, 3, 2, 1
}
Note: Writing 5...1 crashes at runtime because a closed range's lower bound must never exceed its upper bound. If the direction is dynamic, check the values first or use stride(from:to:by:) instead.
Note: Use stride(from:through:by:) when you need a step size other than 1, for example stride(from: 0, through: 10, by: 2) to visit every even number.
SyntaxNameIncludes upper bound?
a...bClosed rangeYes
a..<bHalf-open rangeNo
a...Partial range fromN/A (open-ended)
..<bPartial range up toNo

Exercise: Swift Ranges

Which values are included in the range `1...5`?