# Swift Sequence and Collection Protocols Explained Clearly

Source: https://devdocs10.com/articles/swift/swift-sequence-collection-protocols  
By Murat Bo · Updated 2026-10-17 · DevDocs10 (https://devdocs10.com)

Swift’s `Sequence` and `Collection` protocols are the foundation of how arrays, sets, dictionaries, strings, ranges, and many custom data types are iterated and accessed. If you understand these protocols, you will better understand why some types only support simple iteration, why others support indexing and repeated traversal, and how to design your own custom iterable types in Swift.

> **Quick answer:** In Swift, a `Sequence` is any type you can iterate over, usually with a `for-in` loop. A `Collection` is a more capable kind of sequence that supports stable indexing, multiple passes over its elements, and efficient navigation from one index to another.

> **Difficulty:** Intermediate
>
> **Helpful to know first:** You'll understand this better if you know basic Swift syntax, how `for-in` loops work, and simple types such as `Array`, `Set`, and `Dictionary`.

## 1. What Is Sequence and Collection Protocols?

The Swift standard library uses protocols to describe shared behavior. `Sequence` describes types that can produce elements one at a time. `Collection` builds on that idea and adds stronger guarantees about indexing and traversal.

- `Sequence` means the type can be iterated over.
- `Collection` means the type is also indexable and supports repeated traversal safely.
- Every collection is a sequence, but not every sequence is a collection.
- `Array`, `Set`, and `Dictionary.Values` are collections.
- A custom stream-like type may be a sequence without being a collection.

This distinction matters because many Swift methods are available at the protocol level. For example, both sequences and collections can use methods like `map` and `filter`, but only collections support index-based navigation requirements such as `startIndex`, `endIndex`, and subscripting by index.

## 2. Why Sequence and Collection Protocols Matters

These protocols matter because they let Swift write powerful generic APIs. A function can accept any `Sequence` if it only needs iteration, or any `Collection` if it needs indexing, counting behavior, or multi-pass access.

In real projects, this helps you:

- Write flexible functions that work with arrays, sets, slices, strings, and custom types.
- Choose the right abstraction level for performance and correctness.
- Avoid assuming that all iterable types support integer indexing.
- Build custom data structures that integrate naturally with Swift’s standard library.

Using the right protocol also communicates intent. If your code only needs to loop over elements once, accepting a `Sequence` is enough. If your code needs indexes or repeated passes, `Collection` is the safer requirement.

## 3. Basic Syntax

### Defining a simple Sequence

To create a custom sequence, you must provide a `makeIterator()` method that returns an iterator. The iterator produces elements one by one.

```swift
struct Countdown: Sequence {
    let start: Int

    func makeIterator() -> AnyIterator<Int> {
        var current = start

        return AnyIterator {
            guard current >= 0 else {
                return nil
            }

            defer { current -= 1 }
            return current
        }
    }
}
```

This type can now be used in a `for-in` loop because it satisfies the `Sequence` protocol.

### Defining a simple Collection

A collection needs more than iteration. It needs an index type, start and end indexes, a way to move to the next index, and a subscript.

```swift
struct NameCollection: Collection {
    private let items: [String]

    init(items: [String]) {
        self.items = items
    }

    var startIndex: Int { items.startIndex }
    var endIndex: Int { items.endIndex }

    func index(after i: Int) -> Int {
        items.index(after: i)
    }

    subscript(position: Int) -> String {
        items[position]
    }
}
```

This collection forwards its behavior to an underlying array. It can be iterated over like a sequence, but it also supports indexing and other collection operations.

### Sequence vs Collection in one sentence

If you only need to produce values in order, use `Sequence`. If you also need stable index-based access and repeated traversal, use `Collection`.

## 4. Step-by-Step Examples

### Example 1: Iterating over any Sequence

This function accepts any sequence of integers. It does not care whether the input is an array, a range, or a custom sequence.

```swift
func printSquares<S: Sequence>(from numbers: S) where S.Element == Int {
    for number in numbers {
        print(number * number)
    }
}

printSquares(from: [1, 2, 3])
printSquares(from: 4...6)
```

Output (tested with Swift 6.4):

```text
1
4
9
16
25
36
```

This works because both arrays and closed ranges conform to `Sequence`. The function only asks for iteration, so no collection-specific features are required.

### Example 2: Using Collection indexes safely

Collections do not all use integer indexes in the same way. Swift provides collection APIs so your code works correctly across different collection types.

```swift
let names = ["Ava", "Ben", "Chris"]

let firstIndex = names.startIndex
print(names[firstIndex])

let secondIndex = names.index(after: firstIndex)
print(names[secondIndex])
```

Output (tested with Swift 6.4):

```text
Ava
Ben
```

This example uses the collection API instead of assuming how indexes work internally. That matters especially for strings, where direct integer indexing is not supported.

### Example 3: Sequence methods such as map and filter

Many useful methods are defined on `Sequence`, so custom sequences automatically gain them once they conform correctly.

```swift
let evens = (1...10).filter { $0 % 2 == 0 }
let labels = evens.map { "Even: \($0)" }

print(labels)
```

Output (tested with Swift 6.4):

```text
["Even: 2", "Even: 4", "Even: 6", "Even: 8", "Even: 10"]
```

Because ranges are sequences, they can use methods like `filter` and `map`. This is one reason Swift’s protocol-based design is so powerful.

### Example 4: A custom countdown sequence

Here is the earlier custom sequence in use.

```swift
let countdown = Countdown(start: 3)

for value in countdown {
    print(value)
}
```

Output (tested with Swift 6.4):

```text
3
2
1
0
```

This prints values from 3 down to 0. The type is iterable, so it behaves like other sequence types in Swift.

### Example 5: Why String is a Collection but not integer-indexed

`String` is a collection of characters, but its indexes are not plain integers. Swift does this to handle Unicode correctly.

```swift
let word = "Swift"
let firstCharacter = word[word.startIndex]
print(firstCharacter)

let nextIndex = word.index(after: word.startIndex)
print(word[nextIndex])
```

Output (tested with Swift 6.4):

```text
S
w
```

This shows why collection APIs are preferred over assumptions about index types. A collection guarantees navigation by valid indexes, not necessarily by integers.

## 5. Practical Use Cases

- Accepting any iterable input in a utility function by requiring `Sequence` instead of only `Array`.
- Building custom data structures that work with `for-in`, `map`, `filter`, and `reduce`.
- Writing APIs that need stable indexing, such as pagination helpers, custom list wrappers, or slice-based processing, by requiring `Collection`.
- Working safely with strings and substrings through collection indexes rather than integer offsets.
- Creating lightweight wrapper types around arrays while still exposing standard collection behavior.

## 6. Common Mistakes

### Mistake 1: Assuming every sequence can be indexed

Beginners often treat all iterable types like arrays. But `Sequence` only guarantees iteration, not indexing.

**Problem:** This function claims it only needs a sequence, but then tries to use collection-only features such as `startIndex` and subscripting.

```swift
func printFirst<S: Sequence>(values: S) {
    let first = values[values.startIndex]
    print(first)
}
```

**Fix:** If you only need the first element, ask the sequence’s iterator for it with `makeIterator()` and `next()`. If you need indexing or the `first` property, require `Collection` instead.

```swift
func printFirst<S: Sequence>(values: S) {
    var iterator = values.makeIterator()
    if let first = iterator.next() {
        print(first)
    }
}
```

The corrected version works because `makeIterator()` is available on every sequence and does not require indexing.

### Mistake 2: Using integer indexes with String

Many developers coming from other languages expect strings to support integer-based subscripting. Swift strings do not work that way.

**Problem:** This code tries to use an integer subscript on a string, which causes a compiler error because `String` uses `String.Index`.

```swift
let name = "Swift"
print(name[0])
```

**Fix:** Use the string’s collection indexes and move through them with collection methods.

```swift
let name = "Swift"
let firstIndex = name.startIndex
print(name[firstIndex])
```

Output (tested with Swift 6.4):

```text
S
```

The corrected version works because it uses the proper index type defined by the collection.

### Mistake 3: Accessing endIndex as if it were a valid element position

`endIndex` marks the position just after the last valid element. It is not itself a valid subscript position.

**Problem:** This code attempts to read an element at `endIndex`, which causes a runtime trap for out-of-bounds access.

```swift
let numbers = [10, 20, 30]
print(numbers[numbers.endIndex])
```

**Fix:** Move back from `endIndex` when you want the last element, or use `last`.

```swift
let numbers = [10, 20, 30]
if let lastValue = numbers.last {
    print(lastValue)
}
```

Output (tested with Swift 6.4):

```text
30
```

The corrected version works because `last` safely returns the final element instead of using an invalid index.

### Mistake 4: Requiring Collection when Sequence is enough

Sometimes developers over-constrain APIs. That makes functions less reusable than they need to be.

**Problem:** This function only sums values, but it unnecessarily requires a collection, excluding valid sequence-only types.

```swift
func sumAll<C: Collection>(values: C) -> Int where C.Element == Int {
    values.reduce(0, +)
}
```

**Fix:** Require only `Sequence` when all you need is iteration.

```swift
func sumAll<S: Sequence>(values: S) -> Int where S.Element == Int {
    values.reduce(0, +)
}
```

The corrected version works because it expresses the true minimum requirement and supports more input types.

## 7. Best Practices

### Choose the least restrictive protocol

If your function only loops through values, prefer `Sequence`. If it needs indexing or multi-pass behavior, require `Collection`. This makes generic code more reusable.

```swift
func containsNegative<S: Sequence>(numbers: S) -> Bool where S.Element == Int {
    numbers.contains { $0 < 0 }
}
```

This is better than requiring a collection when you never use collection-specific features.

### Use collection indexes instead of assumptions

Do not assume every collection uses integer indexes or that index movement is always simple arithmetic. Follow the protocol APIs.

```swift
let text = "Hello"
let secondIndex = text.index(text.startIndex, offsetBy: 1)
print(text[secondIndex])
```

Output (tested with Swift 6.4):

```text
e
```

This remains correct for strings because it respects Swift’s Unicode-aware indexing model.

### Wrap storage when building custom collections

If you are learning custom collection conformance, a wrapper around an existing array is often the safest starting point. It lets you focus on protocol requirements without reinventing indexing.

```swift
struct Scores: Collection {
    private var storage: [Int]

    init(_ storage: [Int]) {
        self.storage = storage
    }

    var startIndex: Int { storage.startIndex }
    var endIndex: Int { storage.endIndex }
    func index(after i: Int) -> Int { storage.index(after: i) }
    subscript(position: Int) -> Int { storage[position] }
}
```

This approach lowers the risk of index logic bugs while still giving you a custom type.

## 8. Limitations and Edge Cases

- A `Sequence` may be single-pass in concept, so code should not always assume it can be traversed repeatedly without side effects.
- `Collection` guarantees multi-pass traversal, which is one of its main differences from a plain sequence.
- Not all collections use `Int` as their index type. `String` is the most common beginner surprise.
- `Set` and `Dictionary` are collections, but their iteration order should not be treated as meaningful unless documented for your specific use case.
- A slice such as `ArraySlice` may keep the original collection’s indexes, so its first valid index may not be zero.
- Using `count` on some collections can be less efficient than you expect unless the protocol guarantees stronger performance characteristics.
- If a custom collection implements indexes incorrectly, iteration may skip elements, repeat elements, or trap at runtime.

> A common “not working” situation is expecting an `ArraySlice` to restart at index 0. In Swift, slices usually preserve original indexes, so use `startIndex` rather than hard-coded integer assumptions.

## 9. Practical Mini Project

This small project creates a custom collection that wraps a list of tasks. It behaves like a normal Swift collection, so it supports iteration, subscripting, and standard collection methods.

```swift
struct TaskList: Collection {
    private let tasks: [String]

    init(_ tasks: [String]) {
        self.tasks = tasks
    }

    var startIndex: Int { tasks.startIndex }
    var endIndex: Int { tasks.endIndex }

    func index(after i: Int) -> Int {
        tasks.index(after: i)
    }

    subscript(position: Int) -> String {
        tasks[position]
    }
}

let todayTasks = TaskList([
    "Write report",
    "Review pull request",
    "Send update email"
])

for task in todayTasks {
    print(task)
}

print(todayTasks[todayTasks.startIndex])
print(todayTasks.map { $0.uppercased() })
```

Output (tested with Swift 6.4):

```text
Write report
Review pull request
Send update email
Write report
["WRITE REPORT", "REVIEW PULL REQUEST", "SEND UPDATE EMAIL"]
```

This mini project shows the practical payoff of conforming to `Collection`. Once the protocol requirements are satisfied, your custom type automatically works with many standard library features.

## 10. Key Points

- `Sequence` represents types that can be iterated over.
- `Collection` is a stronger protocol that adds indexing and multi-pass traversal.
- Every collection is also a sequence.
- Use `Sequence` when iteration is enough, and `Collection` when indexing is required.
- Do not assume collection indexes are integers or that `endIndex` is a valid subscript position.
- `String` is a collection, but it uses `String.Index` for Unicode-safe access.
- Custom collections can often be built safely by wrapping an array and forwarding protocol requirements.

## 11. Practice Exercise

Try this exercise to check your understanding.

- Create a custom type named `EvenNumbers` that conforms to `Sequence`.
- It should generate even numbers from 0 up to a maximum value.
- Use it in a `for-in` loop.
- Then call `map` on it to convert the values into strings.

**Expected output:** The loop should print even numbers in order, and the mapped result should contain labels such as `"Value: 0"`, `"Value: 2"`, and so on.

**Hint:** Implement `makeIterator()` and return an iterator that increases by 2 each time until it passes the maximum.

```swift
struct EvenNumbers: Sequence {
    let maximum: Int

    func makeIterator() -> AnyIterator<Int> {
        var current = 0

        return AnyIterator {
            guard current <= maximum else {
                return nil
            }

            let value = current
            current += 2
            return value
        }
    }
}

let evens = EvenNumbers(maximum: 8)

for number in evens {
    print(number)
}

let labels = evens.map { "Value: \($0)" }
print(labels)
```

Output (tested with Swift 6.4):

```text
0
2
4
6
8
["Value: 0", "Value: 2", "Value: 4", "Value: 6", "Value: 8"]
```

## 12. Final Summary

`Sequence` and `Collection` are central to Swift’s standard library design. A sequence gives you ordered iteration, while a collection gives you stronger guarantees such as stable indexes and multi-pass traversal. Understanding that difference helps you write more accurate generic code and avoid common mistakes, especially with strings, slices, and custom iterable types.

As you continue learning Swift collections, pay attention to what a protocol actually guarantees instead of assuming all iterable types behave like arrays. That habit will make your code safer, more reusable, and easier to reason about. A strong next step is to study `BidirectionalCollection`, `RandomAccessCollection`, and slicing behavior such as `ArraySlice` and `Substring`.
