Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Swift  Module 2 – Basics: values, types, operators and control flow

Loops, labels and control transfer

Loop in Swift with for-in over ranges and collections, write while and repeat-while loops that end, and steer nested loops with break, continue and labels.

  • Beginner
  • 20 minutes
  • Examples run with Swift 6.4 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Iterate with for-in over ranges, strides, arrays, strings and dictionaries, with enumerated(), zip and indices
  • Choose between while and repeat-while, and write loops whose exit condition is always reached
  • Use continue and break to control a loop, and statement labels to control nested loops
  • Remove items from an array without skipping any or reading past its end

Before you start

What you’ll learn · Before you start

What you will learn

  • Iterate with for-in over ranges, strides, arrays, strings and dictionaries, with enumerated(), zip and indices
  • Choose between while and repeat-while, and write loops whose exit condition is always reached
  • Use continue and break to control a loop, and statement labels to control nested loops
  • Remove items from an array without skipping any or reading past its end

Before you start

On this page

Four coaches of four seats are 16 seats to check for the first free window seat, and the search in this lesson finds one after looking at 8 of them. Let its break end only the inner loop and it keeps going: it looks at 10 seats and reports three seats in three coaches, when one was asked for.

Here you will loop over ranges, strides, arrays, strings and dictionaries with for-in, number and pair items with enumerated() and zip, write while and repeat-while loops that are sure to end, skip passes with continue and stop with break, reach an outer loop with a label, and remove items from an array without the loop going wrong.

for-in takes each item of a sequence in turn

for i in 1...5 runs its body five times, with i holding 1, then 2, up to 5. The same statement walks through an array’s items, a string’s characters, a stride’s steps or a dictionary’s pairs of key and value: anything that is a sequence: A type whose values a for-in loop can walk through one at a time, such as an array, a range, a stride or a string: a type that conforms to the Sequence protocol.. This program prints part of the 7 times table, adds up three fares, spells out a string, lists departures every 15 minutes, repeats a step three times and prints a dictionary:

for_in.swift: one loop statement, six kinds of sequence Swift · for_in.swift
// for_in.swift: one loop statement, six kinds of sequence.
for i in 1...5 {
    print("7 x \(i) = \(7 * i)")
}

let fares = [20, 35, 50]
var total = 0
for fare in fares {
    total += fare
}
print("total fare:", total)

var spelled = ""
for letter in "PNR" {
    spelled += "\(letter)."
}
print(spelled)

for minute in stride(from: 0, to: 60, by: 15) {
    print("departure at 6:\(minute < 10 ? "0" : "")\(minute)")
}

var stars = ""
for _ in 1...3 {
    stars += "*"
}
print(stars)

let platforms = ["Thane": 4, "Dadar": 3, "Kurla": 1]
for station in platforms.keys.sorted() {
    print(station, "platform", platforms[station] ?? 0)
}

Output

7 x 1 = 7
7 x 2 = 14
7 x 3 = 21
7 x 4 = 28
7 x 5 = 35
total fare: 105
P.N.R.
departure at 6:00
departure at 6:15
departure at 6:30
departure at 6:45
***
Dadar platform 3
Kurla platform 1
Thane platform 4

Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O for_in.swift -o for_in && ./for_in

i, fare, letter and minute are constants that the loop declares itself and sets at the start of every pass; the body can read them but not change them. When the items themselves do not matter, _ takes their place, as in for _ in 1...3, which only repeats the body. Step through how the first loop gets its numbers:

Interactive · stage by stage What for i in 1...5 does on every pass Step through the first loop of for_in.swift. The loop asks the range for one number at a time and stops when there is none left.
What for i in 1...5 does on every pass, step 6 of 6. Showing makeIterator() (on 1...5: done), next() gives an item? (nil: the range is used up), Run the body with it (ran 5 times), The line after the loop (let fares = …). In focus: next() gives an item?.next passmakeIterator()on 1...5: donenext() gives an item?nil: the range is used upRun the body with itran 5 timesThe line after the looplet fares = …

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Start the loop. The loop calls makeIterator() on the range 1...5, once. The iterator remembers where it is in the range, so the code needs no counter of its own.
  2. The first item. next() returns Optional(1). It is not nil, so the loop gives the 1 to i and runs the body, which prints "7 x 1 = 7".
  3. The next pass. Back at the top, next() returns Optional(2), and the body prints "7 x 2 = 14". i is a new constant on every pass; the body cannot change it.
  4. The last item. Passes 3 and 4 go the same way. On the fifth, next() returns Optional(5), and the body prints "7 x 5 = 35".
  5. Nothing left. The sixth call of next() returns nil, because the range has no more numbers. The loop does not run the body again; the program goes on with the line after the loop.
  6. The same for every sequence. An array gives its items in order, a string its characters, a stride its steps, and a dictionary its keys and values, in no fixed order. Each answers next() until it runs out.

The loop calls makeIterator() on the range once and then asks its iterator: The value a for-in loop gets by calling makeIterator() on a sequence. The loop asks it for each next item with next(), which returns nil when there are none left. for one item at a time with next(). An item comes as an optional: a value runs the body once more, and nil ends the loop.

A dictionary has no order of its own, and Swift does not promise the same order from one run of a program to the next. That is why for_in.swift loops over platforms.keys.sorted(), the keys in alphabetical order, and looks each one up with ?? 0, since a lookup returns an optional.

Printing a dictionary in the order of a loop

for (station, platform) in platforms gives every pair, but in an order that can change between runs, so output that depends on it cannot be tested. Sort the keys first, as for_in.swift does, whenever the order shows.

Checkpoint

Check: One item per pass of for-in

  1. Question 1 of 2 In for fare in fares { … }, what is fare?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: A constant that the loop sets to the next item at the start of every pass

    A for-in loop declares its loop constant and sets it at the start of every pass from the sequence's next item. The body reads it but cannot change it.

  2. Question 2 of 2 Why does for_in.swift loop over platforms.keys.sorted() rather than over platforms itself?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: A dictionary has no fixed order, so the printed lines could change from run to run; sorted keys never do

    The contents of a dictionary come out in an order that Swift does not guarantee, even between two runs of the same program. Loop over the sorted keys, and look each value up, whenever the order shows in the output.

enumerated(), zip and indices add numbers and partners

Three helpers cover the common cases of needing more than the item. enumerated() pairs each item with a count from 0, zip walks two sequences side by side, and indices gives the positions of an array. This program uses all three on four stops of a train line, with travel times from this example’s own timetable:

positions.swift: enumerated(), zip and indices Swift · positions.swift
// positions.swift: numbers with enumerated(), partners with zip, and positions with indices.
let stops = ["Churchgate", "Dadar", "Bandra", "Andheri"]
for (number, stop) in stops.enumerated() {
    print(number + 1, stop)
}

let minutes = [0, 14, 22]
for (stop, minute) in zip(stops, minutes) {
    print("\(stop) at +\(minute) min")
}

for i in stops.indices where i > 0 {
    print(stops[i - 1], "->", stops[i])
}

Output

1 Churchgate
2 Dadar
3 Bandra
4 Andheri
Churchgate at +0 min
Dadar at +14 min
Bandra at +22 min
Churchgate -> Dadar
Dadar -> Bandra
Bandra -> Andheri

Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O positions.swift -o positions && ./positions

enumerated() gives a pair on each pass, the count first and then the item, and the loop takes the pair apart into number and stop; number + 1 makes the list start at 1. The zip loop printed three lines, not four. Step through it:

Interactive · step by step How zip pairs four stops with three times Step through the zip loop of positions.swift. Each pass takes the next item of both arrays, and the loop ends when either runs out.
How zip pairs four stops with three times, step 5 of 5. Showing Churchgate, Dadar, Bandra, Andheri (never paired), 0, 14, 22, (none). In focus: Andheri.Churchgatestops[0]Dadarstops[1]Bandrastops[2]Andherinever paired0minutes[0]14minutes[1]22minutes[2](none)minutes has 3 items

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. Two arrays, side by side. zip(stops, minutes) walks through both arrays at once. stops has four items and minutes, in this example's timetable, three.
  2. First pair. The first pass takes the first item of each, ("Churchgate", 0), and the loop names them stop and minute. It prints "Churchgate at +0 min".
  3. Second and third pairs. The next two passes pair Dadar with 14 and Bandra with 22, always the items at the same position of the two arrays.
  4. One side runs out. For a fourth pair, zip asks minutes for a fourth item and gets none. The loop ends at once, without an error, after three passes.
  5. Andheri is left out. The sequence zip makes is as long as the shorter array, so Andheri is never paired and never printed. When two arrays must match, compare their counts before the loop.

zip makes a sequence as long as the shorter of its two inputs, so Andheri, which has no time, was dropped without an error. The last loop needs positions, because each pass looks at the item before as well, and where i > 0 skips the first position, which has no item before it. A where clause on a for runs the body only for the items that pass its condition.

Take positions from indices

stops.indices gives exactly the positions a collection has. A slice such as stops[1...] keeps the positions it had in stops, 1 to 3, so the counts of enumerated(), 0 to 2, and a range 0..<count both miss them, while its indices does not.

Checkpoint

Check: enumerated(), zip and indices

  1. Question 1 of 2 zip(stops, minutes) gets 4 stops and 3 times. How many passes does for (stop, minute) in zip(stops, minutes) make?

    Type a number.

    Show the answer: Question 1 of 2

    Answer: 3 passes

    zip makes a sequence as long as the shorter input. With 3 times there are 3 pairs, so the fourth stop, Andheri in positions.swift, is never paired or printed.

  2. Question 2 of 2 What does this print?

    Read the code, then choose one answer.

    for (n, code) in ["S7", "B3"].enumerated() {
        print(n, code)
    }
    Show the answer: Question 2 of 2

    Answer: 0 S7 1 B3

    enumerated() gives (0, "S7") and then (1, "B3"). The loop splits each pair into n and code, so print shows the count and the code.

while tests before each pass, repeat-while after it

A while loop is for work whose number of passes is not known in advance. This program adds up the digits of 4096 by taking the last digit with % 10 and dropping it with /= 10 until nothing is left, then counts digits in two ways:

digits.swift: a digit sum, and digits counted with while and repeat-while Swift · digits.swift
// digits.swift: while checks before each pass, repeat-while after it.
var n = 4096
var sum = 0
while n > 0 {
    sum += n % 10
    n /= 10
}
print("digit sum of 4096:", sum)

func digitsWhile(_ value: Int) -> Int {
    var rest = value
    var count = 0
    while rest > 0 {
        count += 1
        rest /= 10
    }
    return count
}

func digitsRepeat(_ value: Int) -> Int {
    var rest = value
    var count = 0
    repeat {
        count += 1
        rest /= 10
    } while rest > 0
    return count
}

print("digits of 4096:", digitsWhile(4096), "with while,", digitsRepeat(4096), "with repeat-while")
print("digits of 0:", digitsWhile(0), "with while,", digitsRepeat(0), "with repeat-while")

Output

digit sum of 4096: 19
digits of 4096: 4 with while, 4 with repeat-while
digits of 0: 0 with while, 1 with repeat-while

Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O digits.swift -o digits && ./digits

while rest > 0 tests its condition before every pass, the first one too, so its body may run zero times. repeat { … } while rest > 0 runs the body first and tests afterwards, so its body always runs at least once. For 4096 both counts are 4, but for 0 the while version says 0 digits and the repeat-while version says 1, which is right. Step through both for the value 0:

Interactive · step by step Counting the digits of 0, two ways Step through both functions of digits.swift. The left column tests before the body, the right one after it.
Counting the digits of 0, two ways, step 6 of 6. Showing while rest > 0 (true 4 times, then false), count += 1 (ran 4 times), digitsWhile (returns 4), count += 1 (ran 4 times), while rest > 0 (true 3 times, then false), digitsRepeat (returns 4).truefalsefalsewhile rest > 0true 4 times, then falsecount += 1ran 4 timesdigitsWhilereturns 4count += 1ran 4 timeswhile rest > 0true 3 times, then falsedigitsRepeatreturns 4

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. digitsWhile(0). A while loop tests its condition before every pass, the first one included. rest is 0, and 0 > 0 is false.
  2. The body never runs. The loop ends before its first pass, so count stays 0 and digitsWhile(0) returns 0. But 0 is written with one digit.
  3. digitsRepeat(0). A repeat-while loop runs its body first and tests afterwards. count becomes 1, and rest becomes 0 / 10, which is 0.
  4. The test comes after. Now 0 > 0 is false, so the loop stops after its one pass. digitsRepeat(0) returns 1, the right answer.
  5. For 4096 they agree. For 4096 both loops make four passes, rest going 409, 40, 4 and 0, and both return 4. They differ only when the body would run zero times.
  6. Choose by the first pass. Use repeat-while when the body must run at least once, as for the digits of a number. Use while when zero passes can be right, as for the digit sum of 0.

A loop like these ends only if each pass moves its condition towards false. Here every pass divides rest by 10, and an integer above 0 divided by 10 again and again reaches 0. Write the exit condition first, then check that every path through the body moves towards it.

An update that a path can skip

In while rest > 0 { if rest % 2 == 0 { rest /= 10 } } an odd rest never changes, so the loop never ends. Put the update where every pass runs it, or make every branch change the condition’s value.

Checkpoint

Check: while and repeat-while

  1. Question 1 of 2 Which loop always runs its body at least once?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: repeat { … } while condition

    repeat-while is Swift's do-while: the body runs, then the condition decides whether it runs again. That is why digitsRepeat(0) counted one digit where digitsWhile(0) counted none.

  2. Question 2 of 2 var n = 507, then while n > 0 { n /= 10 }. How many passes does the loop make?

    Type a number.

    A hint

    Write down n after each pass.

    Show the answer: Question 2 of 2

    Answer: 3 passes

    n goes 507, 50, 5 and then 0: three passes, after which 0 > 0 is false. Each pass moves n towards 0, which is why the loop is sure to end.

continue skips the rest of a pass, break ends the loop

continue ends the current pass and starts the next one; break ends the whole loop, and the program goes on after it. This loop adds fares up to a limit of 500, skipping refunds, which are negative, and stopping before the fare that would take the total past the limit:

fare_limit.swift: continue for refunds, break at the limit Swift · fare_limit.swift
// fare_limit.swift: continue skips the rest of a pass, break ends the loop.
let fares = [120, -40, 250, 90, 310, 75]  // a negative fare is a refund
let limit = 500
var total = 0
for fare in fares {
    if fare < 0 {
        continue  // a refund is not a spend: go to the next fare
    }
    if total + fare > limit {
        break  // this fare would pass the limit: stop adding
    }
    total += fare
    print("added \(fare), total \(total)")
}
print("spent \(total) of \(limit)")

var spends = 0
for fare in fares where fare > 0 {
    spends += 1
}
print("fares that are spends:", spends)

let platforms = [Int("3"), Int("TBA"), Int("1")]
for case let platform? in platforms {
    print("announced: platform \(platform)")
}

Output

added 120, total 120
added 250, total 370
added 90, total 460
spent 460 of 500
fares that are spends: 5
announced: platform 3
announced: platform 1

Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O fare_limit.swift -o fare_limit && ./fare_limit

Step through the first loop’s six fares:

Interactive · step by step Six fares, one continue and one break Step through the first loop of fare_limit.swift. Watch where continue sends the loop, and where break sends it.
Six fares, one continue and one break, step 5 of 5. Showing Take the next fare (fare 310), A refund? (no), Past the limit? (770 > 500: break), Add it to the total (total 460), After the loop (spent 460 of 500). In focus: A refund?, Past the limit?.nononext passcontinuebreakTake the next farefare 310A refund?noPast the limit?770 > 500: breakAdd it to the totaltotal 460After the loopspent 460 of 500

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. 120. 120 is not a refund, and 0 + 120 does not pass 500, so it is added. The total is 120.
  2. -40, a refund. -40 < 0, so continue runs. The rest of this pass is skipped, the total stays 120, and the loop goes straight to the next fare.
  3. 250 and 90. Both are spends that fit under the limit, so the total grows to 370 and then 460.
  4. 310 would pass the limit. 460 + 310 = 770, which is more than 500, so break runs. The loop ends at once: 310 is not added, and the last fare, 75, is never even looked at.
  5. Two exits, two meanings. continue ends only the current pass, so the loop goes on with the next fare. break ends the whole loop, so the program goes on after it.

The refund of −40 was skipped by continue, the total reached 460, and 310 would have made it 770, so break ended the loop. The last fare, 75, was never looked at, even though it would have fitted: a break stops the loop, not just one fare. The second loop counts the spends with for fare in fares where fare > 0, a where clause that does what an if … continue at the top of the body would. The last loop is a preview of the module on pattern matching: for case let platform? in platforms runs only for the optionals that hold a value, here 3 and 1, and skips the nil.

Key idea

continue leaves the current pass and the loop goes on; break leaves the loop. A condition for skipping can also sit on the loop itself, as a where clause.

Checkpoint

Check: continue and break

  1. Question 1 of 2 A loop over seat numbers must skip seat 13 and stop at the first seat above 60. Which statements does it use?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: continue for seat 13, break above 60

    Use continue to leave the current pass and break to leave the loop. Skipping one seat is a pass; stopping the search is the loop.

  2. Question 2 of 2 In fare_limit.swift, the limit changes from 500 to 400. What total does the first loop reach?

    Type a number.

    A hint

    Follow the six fares with the new limit, and remember what break does.

    Show the answer: Question 2 of 2

    Answer: 370 rupees

    120 makes 120, the refund of -40 is skipped by continue, and 250 makes 370. Then 370 + 90 = 460 is more than 400, so break ends the loop at 370, and 310 and 75 are never looked at.

A label lets break and continue reach an outer loop

A break inside a nested loop ends only the innermost loop it is in. To find the first free window seat in four coaches, the search must stop both the seat loop and the coach loop. This program runs the search twice, once with a plain break and once with a statement label: A name and a colon before a loop, if or switch, as in search: for coach in …, so that break search or continue search inside a nested loop acts on that statement., search:, in front of the outer loop and break search inside:

seat_search.swift: the first free window seat, without a label and with one Swift · seat_search.swift
// seat_search.swift: the first free window seat, without a label and with one.
let window = [true, false, false, true]  // seats 1 and 4 of every coach are at a window
let taken = [
    [true, false, false, true],  // coach 1: both window seats taken
    [true, true, false, false],  // coach 2: seat 4 is free
    [false, true, true, true],   // coach 3: seat 1 is free
    [false, false, true, true],  // coach 4: seat 1 is free
]

var checks = 0
print("Without a label:")
for coach in 0..<taken.count {
    for seat in 0..<window.count {
        checks += 1
        if !window[seat] {
            continue
        }
        if !taken[coach][seat] {
            print("  coach \(coach + 1), seat \(seat + 1)")
            break  // leaves the seat loop only
        }
    }
}
print("  \(checks) seats looked at")

checks = 0
print("With a label:")
search: for coach in 0..<taken.count {
    for seat in 0..<window.count {
        checks += 1
        if !window[seat] {
            continue
        }
        if !taken[coach][seat] {
            print("  coach \(coach + 1), seat \(seat + 1)")
            break search  // leaves both loops
        }
    }
}
print("  \(checks) seats looked at")

Output

Without a label:
  coach 2, seat 4
  coach 3, seat 1
  coach 4, seat 1
  10 seats looked at
With a label:
  coach 2, seat 4
  8 seats looked at

Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O seat_search.swift -o seat_search && ./seat_search

Without the label, break left the seat loop only, so the coach loop went on to coaches 3 and 4 and found a seat in each: three seats, 10 looked at. With break search, both loops stopped at coach 2, seat 4, after 8. Step through the labelled search:

Interactive · stage by stage How break search leaves both loops Step through the labelled search of seat_search.swift. continue skips an aisle seat, and break search ends both loops at the first free window seat.
How break search leaves both loops, step 5 of 5. Showing search: next coach (goes on to coaches 3, 4), Next seat of the coach (seats 1 to 4), A free window seat? (seat 4: free window), After both loops (unlabelled: 10, 3 seats). In focus: Next seat of the coach, search: next coach.no: next seatcoach donesearch: next coachgoes on to coaches 3, 4Next seat of the coachseats 1 to 4A free window seat?seat 4: free windowAfter both loopsunlabelled: 10, 3 seats

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. Coach 1. The outer loop takes coach 1 and the inner loop its four seats. Seats 1 and 4 are window seats but taken, and seats 2 and 3 are aisle seats, which continue skips.
  2. On to coach 2. The inner loop has run out of seats, so the outer loop takes its next coach. The count of seats looked at is 4 so far.
  3. Seat 4 is free. Seat 1 of coach 2 is a taken window seat, seats 2 and 3 are aisle seats, and seat 4 is a free window seat, after 8 seats in all. The program prints "coach 2, seat 4".
  4. break search. break search ends the loop labelled search, the outer one, so both loops stop at once. Coaches 3 and 4 are never looked at, and the program prints "8 seats looked at".
  5. Without the label. A plain break ends only the innermost loop, the seat loop. The coach loop would go on to coaches 3 and 4, find a seat in each, and print three seats after 10 looked at.

continue takes a label in the same way. continue search would end the current pass of the coach loop and go on with the next coach, skipping the coach’s remaining seats, which is how a search moves past a coach that is closed. Labels can also name an if or a switch for break, and every label in a nest must be different. The Swift Programming Language notes that a label does no harm where a plain continue would act on the right loop anyway, and that it can make the logic clearer to read.

Checkpoint

Check: Labels on nested loops

  1. Question 1 of 2 In the search without a label, what does the break inside the seat loop end?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Only the seat loop; the coach loop goes on with its next coach

    An unlabelled break acts on the innermost enclosing loop. That is why the unlabelled search printed three seats after looking at 10, where the labelled one printed one seat after 8.

  2. Question 2 of 2 In seat_search.swift, nothing follows the seat loop inside the coach loop. Which statements, written inside the seat loop, move the search straight on to the next coach? Choose every one that does.

    Choose every answer that is right.

    Show the answer: Question 2 of 2

    Answer:

    • continue search
    • break

    continue search goes to the next pass of the labelled loop. Here a plain break has the same effect, but only because the coach loop does nothing after its seat loop; the label states the intent whatever follows.

Change an array after the loop, not under its positions

A for-in loop over an array reads the array as it was when the loop began. Arrays are values: the loop keeps the array it was given at the start, and a change in the body goes to the variable, not to the array the loop is reading. This program appends inside such a loop, and then removes items by building a new array:

own_copy.swift: appending inside a for-in loop, and removing by building a new array Swift · own_copy.swift
// own_copy.swift: a for-in loop reads the array as it was when the loop began.
var queue = ["PNR41", "PNR57"]
for booking in queue {
    queue.append(booking + "-return")
}
print(queue)

var bookings = ["PNR41", "CANCELLED", "CANCELLED", "PNR57", "PNR63"]
var kept: [String] = []
for booking in bookings where booking != "CANCELLED" {
    kept.append(booking)
}
bookings = kept
print(bookings)

Output

["PNR41", "PNR57", "PNR41-return", "PNR57-return"]
["PNR41", "PNR57", "PNR63"]

Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O own_copy.swift -o own_copy && ./own_copy

The loop over queue ran twice, not forever, although it appended two items: it read the two-item array it started with. Removing by position is different, because a range of positions such as 0..<bookings.count is worked out once, before the first pass, and a removal moves every later item up one place:

remove_by_index.swift: remove(at:) inside a loop over 0..<count Swift · remove_by_index.swift
// remove_by_index.swift: removing items inside a loop over the positions the array had at the start.
var bookings = ["PNR41", "CANCELLED", "CANCELLED", "PNR57", "PNR63"]
for i in 0..<bookings.count {
    print("checking position", i)
    if bookings[i] == "CANCELLED" {
        bookings.remove(at: i)
    }
}
print(bookings)
remove_by_index.swift in a debug build and a release build Python · remove_session.py
from session import run, version

# remove_by_index.swift in a debug build and in a release build.
version()
run("""
swiftc -Onone remove_by_index.swift -o remove_by_index
!./remove_by_index
swiftc -O remove_by_index.swift -o remove_by_index
!./remove_by_index
""")

Output

# Apple Swift version 6.4 (swiftlang-6.4.0.34.1)
$ swiftc -Onone remove_by_index.swift -o remove_by_index
$ ./remove_by_index
checking position 0
checking position 1
checking position 2
checking position 3
checking position 4
Swift/ContiguousArrayBuffer.swift:692: Fatal error: Index out of range
[exit status 133]
$ swiftc -O remove_by_index.swift -o remove_by_index
$ ./remove_by_index
checking position 0
checking position 1
checking position 2
checking position 3
checking position 4
[exit status 133]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 remove_session.py

Both builds stopped with exit status 133 after checking position 4. Step through what happened to the positions:

Interactive · step by step Removing items inside a loop over positions Step through remove_by_index.swift. The left column is the range 0..<5, worked out once; the right column is what each position holds now.
Removing items inside a loop over positions, step 7 of 7. Showing position 0, position 1, position 2, position 3, position 4, PNR41, CANCELLED (now: the second CANCELLED), CANCELLED (now: PNR57), PNR57 (now: PNR63), PNR63 (bookings[4]: out of range).position 0position 1position 2position 3position 4PNR41CANCELLEDnow: the second CANCELLEDCANCELLEDnow: PNR57PNR57now: PNR63PNR63bookings[4]: out of range

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. The range is fixed. for i in 0..<bookings.count works out the range once, before the first pass: 0..<5, positions 0 to 4. Removing items later does not shorten it.
  2. i = 0. Position 0 holds PNR41, which stays.
  3. i = 1, a removal. Position 1 holds CANCELLED, so remove(at: 1) runs. Every item after it moves up one place: the second CANCELLED is now at position 1, PNR57 at 2, PNR63 at 3, and position 4 is empty.
  4. i = 2 skips one. The loop reads position 2, PNR57. The second CANCELLED moved into position 1, which the loop has already passed, so it is never checked.
  5. i = 3. Position 3 holds PNR63, which stays. The array now has four items, at positions 0 to 3.
  6. i = 4 traps. The range still gives 4, but no position 4 is left. bookings[4] stops the program with "Index out of range", exit status 133.
  7. The fix. Build a new array of the items to keep, as own_copy.swift does with for … where, or call removeAll(where:). Neither reads positions that the removals move.

The second CANCELLED moved into position 1, which the loop had passed, so it was never checked, and the range still offered position 4 after the array had shrunk to four items. Reading bookings[4] was a trap: An immediate stop of a program at a failed runtime check, such as an integer overflow or a division by zero. No line after it runs; a shell reports the stop as an exit status of 128 plus the signal's number. More in “Integers, overflow traps and wrapping operators”: Swift checks every index and stops the program rather than read outside an array. Build a new array of the items to keep, as own_copy.swift does with for booking in bookings where booking != "CANCELLED", or call bookings.removeAll(where:), which the module on closures covers.

Checkpoint

Check: Changing an array while looping

  1. Question 1 of 2 var queue = ["A", "B", "C"], then for b in queue { queue.append(b) }. How many items does queue hold afterwards?

    Type a number.

    Show the answer: Question 1 of 2

    Answer: 6 items

    The loop reads the array as it was when the loop began, three items, so it makes three passes and appends three items: 6 in all. It never sees its own appends, which is why it does not run forever.

  2. Question 2 of 2 Why did remove_by_index.swift stop when it reached position 4?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The range 0..<5 was fixed before the first pass, but after one removal the array had only positions 0 to 3

    0..<bookings.count is worked out once, as 0..<5. One removal made the array four items long and moved every later item up a place, so the loop skipped the second CANCELLED and then read past the end, which traps.

Practise: the first repeated booking and the primes up to n

Write two functions. firstRepeat(_:) takes booking IDs in the order they arrived and returns the first ID that appears a second time, or nil when none does; use a loop over the positions and an inner loop over the earlier ones. primes(upTo:) returns the primes up to a limit: for each number from 2, try to divide it by the primes found so far, skip to the next number at the first divisor, and stop trying once a prime’s square passes the number. Return an empty array for a limit below 2, because 2...1 is not a valid range.

Write and test yours before reading this version:

practice.swift: one way to write it Swift · practice.swift
// practice.swift: the first repeated booking and the primes up to a limit.
func firstRepeat(_ ids: [String]) -> String? {
    for i in ids.indices {
        for j in 0..<i where ids[j] == ids[i] {
            return ids[i]
        }
    }
    return nil
}

func primes(upTo limit: Int) -> [Int] {
    guard limit >= 2 else {
        return []
    }
    var found: [Int] = []
    candidates: for n in 2...limit {
        for p in found {
            if p * p > n {
                break  // no divisor up to the square root: n is prime
            }
            if n % p == 0 {
                continue candidates  // a divisor: try the next n
            }
        }
        found.append(n)
    }
    return found
}

print(firstRepeat(["PNR41", "PNR57", "PNR63", "PNR57", "PNR41"]) ?? "none")
print(firstRepeat(["PNR41", "PNR57"]) ?? "none")
print(primes(upTo: 30))
print(primes(upTo: 1))

Output

PNR57
none
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
[]

Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O practice.swift -o practice && ./practice

firstRepeat(_:) returns from inside both loops, which ends them at once, as a labelled break would. primes(upTo:) needs both kinds of jump: break in the inner loop once no divisor is possible, and continue candidates, with a label, to leave the inner loop and move the outer loop to its next number at the first divisor. Finish with the practice round below. It asks first about the part of this lesson you have missed most often, and raises the level as your answers improve:

Practice that adapts to you

Practise this lesson

12 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 12 What does for _ in 1...3 { … } do?

    Choose one answer.

    Show the answer: Question 1 of 12

    Answer: It runs the body three times without naming the numbers

    When the items do not matter, _ takes the place of the loop constant. for_in.swift used it to add three stars.

  2. Question 2 of 12 What does this print?

    Read the code, then choose one answer.

    for m in stride(from: 10, to: 0, by: -4) {
        print(m)
    }
    Show the answer: Question 2 of 12

    Answer: 10 6 2

    stride(from:to:by:) begins at its start and adds the step until the next number would reach or pass the end. From 10 by -4 that is 10, 6 and 2.

  3. Question 3 of 12 zip(names, marks) gets 5 names and 4 marks. What happens to the fifth name?

    Choose one answer.

    Show the answer: Question 3 of 12

    Answer: It is never paired, and the loop makes 4 passes

    The sequence zip makes is as long as its shorter input, so items of the longer one are dropped quietly. Compare the counts first when the two arrays must match.

  4. Question 4 of 12 What does this print?

    Read the code, then choose one answer.

    let fares = [20, 35, 50]
    for i in fares.indices where i > 0 {
        print(fares[i] - fares[i - 1])
    }
    Show the answer: Question 4 of 12

    Answer: 15 15

    A where clause filters the items a for loop gives its body. Here it removes position 0, so every pass can safely look at the position before its own.

  5. Question 5 of 12 What does digits.swift print?

    What does this program print? Choose one answer.

    // digits.swift: while checks before each pass, repeat-while after it.
    var n = 4096
    var sum = 0
    while n > 0 {
        sum += n % 10
        n /= 10
    }
    print("digit sum of 4096:", sum)
    
    func digitsWhile(_ value: Int) -> Int {
        var rest = value
        var count = 0
        while rest > 0 {
            count += 1
            rest /= 10
        }
        return count
    }
    
    func digitsRepeat(_ value: Int) -> Int {
        var rest = value
        var count = 0
        repeat {
            count += 1
            rest /= 10
        } while rest > 0
        return count
    }
    
    print("digits of 4096:", digitsWhile(4096), "with while,", digitsRepeat(4096), "with repeat-while")
    print("digits of 0:", digitsWhile(0), "with while,", digitsRepeat(0), "with repeat-while")
    Show the answer: Question 5 of 12

    Answer: it prints

    digit sum of 4096: 19
    digits of 4096: 4 with while, 4 with repeat-while
    digits of 0: 0 with while, 1 with repeat-while

    The digit sum takes 6, 9, 0 and 4 off the end of 4096 and adds them: 19. The two digit counts agree for 4096 and differ for 0, where only repeat-while runs its body.

  6. Question 6 of 12 Which loop is sure to end for every starting value of n that is 0 or more?

    Choose one answer.

    Show the answer: Question 6 of 12

    Answer: while n > 0 { n /= 2 }

    A loop ends when every pass moves its condition towards false and the condition is sure to get there. Check the value that starts at the edge, such as 0, and every branch of the body.

  7. Question 7 of 12 What does continue do in a loop?

    Choose one answer.

    Show the answer: Question 7 of 12

    Answer: It ends the current pass, and the loop goes on with its next pass

    continue says "this pass is done": in a for loop the next item comes, in a while loop the condition is tested again. fare_limit.swift used it to skip the refund of -40.

  8. Question 8 of 12 Which loop prints the same as for f in fares { if f <= 0 { continue }; print(f) }?

    Choose one answer.

    Show the answer: Question 8 of 12

    Answer: for f in fares where f > 0 { print(f) }

    A where clause on a for loop and an if … continue at the top of its body both skip the items that fail a test. The where clause says it on the loop itself.

  9. Question 9 of 12 How do you give a loop a name that break and continue can use?

    Choose one answer.

    Show the answer: Question 9 of 12

    Answer: outer: for x in xs { … }

    Write the label and a colon before the loop, as search: for coach in … in seat_search.swift, and then break search or continue search inside it.

  10. Question 10 of 12 What does this print?

    Read the code, then choose one answer.

    outer: for i in 1...3 {
        for j in 1...3 {
            if j == 2 { continue outer }
            if i == 3 { break outer }
            print(i, j)
        }
    }
    Show the answer: Question 10 of 12

    Answer: 1 1 2 1

    continue outer ends the current pass of the outer loop, skipping the rest of the inner loop, and break outer ends both loops. So only (1, 1) and (2, 1) are printed.

  11. Question 11 of 12 A for-in loop over an array appends to that same array in its body. What happens?

    Choose one answer.

    Show the answer: Question 11 of 12

    Answer: The loop makes one pass for each item the array had when the loop began

    own_copy.swift appended two items inside a loop over a two-item array, and the loop made exactly two passes. Each for-in loop reads the array as it began.

  12. Question 12 of 12 Which code removes every CANCELLED booking without skipping any and without a trap?

    Choose one answer.

    Show the answer: Question 12 of 12

    Answer: Append each booking that is not CANCELLED to a new array kept, with for b in bookings where b != "CANCELLED", then set bookings to kept

    Removing by position inside a loop moves the items the loop has yet to read. Building a new array, or bookings.removeAll(where:), avoids the problem entirely.

Interview questions

Warm-up: what is the difference between while and repeat-while in Swift?

A while loop tests its condition before every pass, so its body can run zero times; a repeat-while loop runs its body first and tests afterwards, so the body runs at least once. Swift’s repeat-while is what other languages call do-while. The choice matters exactly when the condition is false from the start. Counting the digits of a number by dividing it by 10 shows it: for 0, a while loop makes no pass and answers 0, while repeat-while makes one pass and answers 1, which is right. Use repeat-while when one pass must always happen, such as asking for input until it is valid, and while when no pass at all can be the right result. In both, every pass has to move the condition towards false, or the loop never ends.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A clean-up job runs for i in 0..<bookings.count and calls bookings.remove(at: i) for each cancelled booking. It stops with Index out of range, and a version that first checks i < bookings.count no longer stops but leaves some cancelled bookings behind. Explain both symptoms with a small example, and give two correct ways to remove them.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to mid

    A seat search loops over 20 coaches of 72 seats, collects every free window seat it meets and then uses only the first. How many seats does it look at when the first free window seat is coach 2, seat 10? How would you make it stop at the first match, with a label and without?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • for-in walks any sequence, a range, a stride, an array, a string or a dictionary, by asking its iterator for one item at a time until it gives nil; sort a dictionary’s keys first whenever the order shows.
  • enumerated() pairs items with a count from 0, zip pairs two sequences and stops at the shorter one, and indices gives an array’s positions.
  • while tests before each pass and may run zero times, repeat-while tests after and runs at least once, and in both every pass must move the condition towards false.
  • continue ends the current pass and break the whole loop, and a where clause on a for loop filters its items.
  • A plain break or continue acts on the innermost loop; a statement label such as search: lets them act on an outer loop.
  • A for-in loop over an array reads the array as it began, but removing items inside a loop over 0..<count skips items and then traps, so build a new array or use removeAll(where:).

Terms in this lesson

iterator
The value a for-in loop gets by calling makeIterator() on a sequence. The loop asks it for each next item with next(), which returns nil when there are none left.
sequence
A type whose values a for-in loop can walk through one at a time, such as an array, a range, a stride or a string: a type that conforms to the Sequence protocol.
statement label
A name and a colon before a loop, if or switch, as in search: for coach in …, so that break search or continue search inside a nested loop acts on that statement.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.