Swift has no C-style for (int i = 0; i < 10; i += 2) loop. Ranges like 0..<10 cover steps of one, and for everything else there’s stride: counting by twos, counting down, stepping through decimals, or walking every third element of an array. Behind it is a small protocol, Strideable, which your own types can adopt too.
stride in Swift
to vs. through
There are two stride functions, and the only difference is whether the end value can be included:
for i in stride(from: 0, to: 10, by: 2) {
print(i) // 0, 2, 4, 6, 8: stops BEFORE 10
}
for i in stride(from: 0, through: 10, by: 2) {
print(i) // 0, 2, 4, 6, 8, 10: includes 10 when a step lands on it
}
An easy way to remember it: to works like ..<, and through works like ....
through only includes the end value if the steps land on it exactly:
print(Array(stride(from: 0, through: 9, by: 2))) // [0, 2, 4, 6, 8]
Counting down
Use a negative step:
for seconds in stride(from: 3, through: 1, by: -1) {
print("\(seconds)…")
}
print("Liftoff!")
print(Array(stride(from: 10, to: 0, by: -3))) // [10, 7, 4, 1]
For a simple countdown by one, reversed() on a range reads just as well, and is equally efficient: it doesn’t build an array, it just walks the range backwards.
for seconds in (1...3).reversed() {
print(seconds)
}
Floating-point strides
stride works with any Strideable type, including Double, Float, and CGFloat:
for angle in stride(from: 0.0, through: 1.0, by: 0.25) {
print(angle) // 0.0, 0.25, 0.5, 0.75, 1.0
}
Steps like 0.25 are exactly representable in binary. Steps like 0.1 are not, and that’s where stride quietly protects you. Compare it with a hand-written loop that keeps adding the step:
let viaStride = Array(stride(from: 0.0, to: 1.0, by: 0.1))
var accumulator = 0.0
var viaAdding: [Double] = []
while accumulator < 1.0 {
viaAdding.append(accumulator)
accumulator += 0.1
}
print(viaStride.count, viaAdding.count) // 10 11
print(viaStride.last!, viaAdding.last!) // 0.9 0.9999999999999999
Repeated addition accumulates rounding error: after ten additions, the value is 0.9999999999999999, which is still less than 1.0, so the loop runs an extra time. stride instead computes each element as start + n * step, so errors don’t pile up and you get the ten values you meant.
It can’t make 0.1 exact, though. The comparison with the end value can still land on either side:
let tenths = Array(stride(from: 0.0, through: 0.3, by: 0.1))
print(tenths.count) // 3, not 4: 3 × 0.1 is 0.30000000000000004, just above 0.3
When you need an exact number of steps, stride over integers and convert:
let exactTenths = (0...10).map { Double($0) / 10 } // always 11 values, 0.0 to 1.0
Strides are lazy sequences
stride(from:to:by:) returns a StrideTo<T>, and stride(from:through:by:) returns a StrideThrough<T>. Both are sequences that compute values on demand; they don’t allocate an array. Even an enormous stride costs nothing until you iterate it:
let everyThousand = stride(from: 0, to: 1_000_000_000, by: 1_000)
print(everyThousand.first(where: { $0 > 5_500 }) ?? 0) // 6000, after 7 steps
All the usual sequence tools work: map, filter, reduce, contains, first(where:), zip, and so on.
let sumOfOdds = stride(from: 1, through: 99, by: 2).reduce(0, +)
print(sumOfOdds) // 2500
Practical examples
Every n-th element
let frames = Array(0..<20)
let keyFrames = stride(from: 0, to: frames.count, by: 5).map { frames[$0] }
print(keyFrames) // [0, 5, 10, 15]
Chunking an array
let items = Array(1...10)
let chunks = stride(from: 0, to: items.count, by: 3).map {
Array(items[$0..<min($0 + 3, items.count)])
}
print(chunks) // [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
Pairs of neighbors
let readings = [3, 8, 4, 9, 2, 7]
let pairs = stride(from: 0, to: readings.count - 1, by: 2).map { (readings[$0], readings[$0 + 1]) }
print(pairs) // [(3, 8), (4, 9), (2, 7)]
Grid coordinates
let spacing = 50
var points: [(Int, Int)] = []
for x in stride(from: 0, through: 100, by: spacing) {
for y in stride(from: 0, through: 50, by: spacing) {
points.append((x, y))
}
}
print(points.count) // 6: x in 0, 50, 100 and y in 0, 50
How it works: the Strideable protocol
stride works with any type conforming to Strideable, which needs two things:
- a
Stridetype describing a distance (forInt, it’sInt; forDouble, it’sDouble), distance(to:)andadvanced(by:)to measure and move.
You can make your own types strideable:
struct Page: Strideable {
let number: Int
func distance(to other: Page) -> Int {
other.number - number
}
func advanced(by n: Int) -> Page {
Page(number: number + n)
}
}
for page in stride(from: Page(number: 1), through: Page(number: 7), by: 3) {
print("Page \(page.number)") // Page 1, Page 4, Page 7
}
There’s a bonus: when a type’s Stride is an integer type, ranges of that type become countable, so you can iterate them and use map directly:
let firstPages = Page(number: 1)...Page(number: 3)
print(firstPages.map { $0.number }) // [1, 2, 3]
Strideable also gives you < and == through its Comparable conformance, based on distance(to:).
Beyond arithmetic steps
stride always adds the same amount. For other progressions, like doubling or following a chain of objects, use sequence(first:next:), which is just as lazy:
let powersOfThree = Array(sequence(first: 1, next: { $0 < 100 ? $0 * 3 : nil }))
print(powersOfThree) // [1, 3, 9, 27, 81, 243]
Pitfalls
- A step of zero is a programming error and traps at runtime.
- A step with the wrong sign gives an empty sequence, not an error:
stride(from: 0, to: 10, by: -1)produces nothing. If a stride mysteriously does nothing, check the sign. - Indices: when striding over array indices, use
to: array.count(exclusive) to stay in bounds, and remember that a neighbor-pair stride needscount - 1. - Floating-point ends: don’t rely on
throughhitting a non-representable end value.
Interview questions
What’s the difference between stride(from:to:by:) and stride(from:through:by:)?
to never includes the end value, like ..<; through includes it if a step lands on it exactly, like ....
Why is stride better than adding a Double step in a loop?
stride computes each element as start + n * step, so rounding errors don’t accumulate across iterations.
Does stride create an array?
No. It returns a lazy sequence (StrideTo or StrideThrough) that computes values as you iterate.
How do you make your own type work with stride?
Conform to Strideable by implementing distance(to:) and advanced(by:).
Summary
stride(from:to:by:)excludes the end;stride(from:through:by:)includes it when a step lands on it.- Negative steps count down; a zero step traps; a wrong-sign step yields nothing.
- Floating-point strides compute
start + n * step, avoiding accumulated error, but can’t make non-representable steps exact. - Strides are lazy sequences, cheap even for huge ranges.
- Any
Strideabletype works withstride, and integer-strided types get countable ranges too.