Swift Slices and Subsequences: How ArraySlice Works
Swift collections let you work with only part of a collection without immediately copying all its elements. That feature appears as slices and subsequences. Understanding how they work is important because a slice often looks like an array, but it is not the same type and it keeps the original collection’s indexing behavior. In this article, you will learn what Swift slices and subsequences are, how to create them, when to use them, and what mistakes beginners commonly run into.
Quick answer: In Swift, a slice is a view into part of a collection, and a subsequence is the type returned when you take that part. For arrays, the subsequence type is usually ArraySlice. It shares storage with the original collection and keeps the original indexes instead of starting at zero.
Difficulty: Intermediate
Helpful to know first: You will understand this better if you already know basic Swift arrays, ranges such as 0...2 and 1..<4, and how collection indexes work at a beginner level.
1. What Is a Slice or Subsequence?
A slice is a partial view of a collection. Instead of creating a brand-new collection with copied values, Swift can return a subsection of an existing collection. The general name for that returned partial collection is a SubSequence.
For example, when you take part of an Array, Swift usually gives you an ArraySlice. That slice contains some of the original elements, but it is not the same thing as a full Array.
- A slice represents only part of a collection.
- A subsequence is the type returned for that partial view.
- For arrays, the subsequence type is commonly ArraySlice.
- Slices often share storage with the original collection.
- A slice keeps the original index positions rather than automatically restarting at zero.
That last point is the one that surprises many developers. If you slice an array starting at index 2, the first valid index in the slice is still 2, not 0.
2. Why Slices and Subsequences Matter
Slices matter because they make it efficient to work with part of a collection. If you only need a few elements from a larger array or string-based collection, a slice can avoid unnecessary copying.
They are especially useful when you want to:
- Process only the first few or last few items of a collection.
- Skip elements with methods like dropFirst() or dropLast().
- Split parsing work into smaller pieces.
- Pass a partial collection into helper logic without building a new array immediately.
However, slices are not always the best final type to keep around long term. Because they usually reference the original collection’s storage, keeping a small slice can also keep a much larger original collection alive in memory.
3. Basic Syntax or Core Idea
Creating a slice with a range
The simplest way to create a slice is to use a range on a collection.
let numbers = [10, 20, 30, 40, 50]
let middle = numbers[1..<4]
print(middle)
print(type(of: middle))
This creates a slice containing the elements at indexes 1, 2, and 3. The result is an ArraySlice<Int>, not an Array<Int>.
Understanding the indexes
Even though middle contains three elements, its indexes still match the original array.
let numbers = [10, 20, 30, 40, 50]
let middle = numbers[1..<4]
print(middle.startIndex)
print(middle[1])
The first print call shows the starting index of the slice. The second line works because index 1 is valid in this slice. Trying to use index 0 here would fail.
Converting a slice into a new array
If you want a separate array with zero-based indexing, create one explicitly.
let numbers = [10, 20, 30, 40, 50]
let middleSlice = numbers[1..<4]
let middleArray = Array(middleSlice)
print(middleArray[0])
This creates a new array that contains the sliced values as its own collection.
4. Step-by-Step Examples
Example 1: Taking a range from an array
This is the most direct slice example. It returns part of an array without building a new array first.
let scores = [85, 90, 78, 92, 88]
let selectedScores = scores[1...3]
print(selectedScores)
The slice contains 90, 78, and 92. Because the range is closed with ..., the end index is included.
Example 2: Using prefix and suffix
Swift has convenient methods for taking the beginning or end of a collection.
let letters = ["A", "B", "C", "D", "E"]
let firstThree = letters.prefix(3)
let lastTwo = letters.suffix(2)
print(firstThree)
print(lastTwo)
Both prefix and suffix return subsequences. For an array, that means slices rather than full arrays.
Example 3: Dropping elements
Dropping values is another common way to get a subsequence.
let queue = ["first", "second", "third", "fourth"]
let remaining = queue.dropFirst()
print(remaining)
dropFirst() returns everything except the first element. The result is still a subsequence view of the original collection.
Example 4: Iterating safely over a slice
When you are reading values, it is often best to iterate directly instead of assuming zero-based indexes.
let temperatures = [18, 20, 22, 24, 26]
let afternoon = temperatures[2...4]
for value in afternoon {
print(value)
}
This works well because it does not depend on any specific indexes. That makes your code easier to reason about.
5. Practical Use Cases
- Reading just the first few results from a larger search result array.
- Skipping a header row and processing the remaining records.
- Taking the last few log entries for display in a debugging tool.
- Parsing token streams where helper functions consume only part of a collection.
- Breaking large data sets into smaller windows for analysis.
- Working with prefixes or suffixes before deciding whether a full copy is necessary.
6. Common Mistakes
Mistake 1: Assuming a slice starts at index 0
This is the most common problem with ArraySlice. A slice keeps the indexes from the original array.
Problem: This code assumes the first valid index of the slice is zero, but the slice actually starts at index 1. That causes an out-of-bounds access at runtime.
let numbers = [10, 20, 30, 40]
let part = numbers[1...2]
print(part[0])
Fix: Use the slice’s real indexes, or convert it to an array if you need zero-based indexing.
let numbers = [10, 20, 30, 40]
let part = numbers[1...2]
print(part[part.startIndex])
let copiedPart = Array(part)
print(copiedPart[0])
The corrected version works because it respects the slice’s actual index model or creates a new array with fresh indexing.
Mistake 2: Expecting an Array when Swift returns ArraySlice
Many methods such as prefix, suffix, and dropFirst return subsequences, not full arrays.
Problem: This code tries to assign an ArraySlice<Int> to a variable typed as [Int], which leads to a type mismatch such as “Cannot convert value of type 'ArraySlice<Int>' to specified type '[Int]'”.
let values = [1, 2, 3, 4]
let firstTwo: [Int] = values.prefix(2)
Fix: Either keep the result as a slice or convert it explicitly to an array.
let values = [1, 2, 3, 4]
let firstTwoSlice = values.prefix(2)
let firstTwoArray: [Int] = Array(values.prefix(2))
The corrected version works because it matches the actual return type or performs an explicit conversion.
Mistake 3: Keeping a small slice for too long
A slice often shares the original collection’s storage. That is efficient for short-term work, but it can be wasteful if the original collection is very large and you keep only a tiny slice.
Problem: This code stores a tiny slice from a large array and may keep the large array’s storage alive longer than expected.
let bigArray = Array(1...100000)
let smallView = bigArray[0...2]
Fix: If you need to store only the small result long term, convert it into a new array.
let bigArray = Array(1...100000)
let smallView = Array(bigArray[0...2])
The corrected version works because the new array owns only the elements you want to keep.
Mistake 4: Confusing ArraySlice with Array in APIs
Some functions are written to accept only arrays, even though a slice would contain compatible elements.
Problem: This code passes an ArraySlice<String> to a function that expects [String], so the call fails to compile.
func printNames(_ names: [String]) {
for name in names {
print(name)
}
}
let allNames = ["Ana", "Ben", "Cara"]
let someNames = allNames[1...2]
printNames(someNames)
Fix: Convert the slice to an array, or design the function to accept a broader collection type when appropriate.
func printNames(_ names: [String]) {
for name in names {
print(name)
}
}
let allNames = ["Ana", "Ben", "Cara"]
let someNames = Array(allNames[1...2])
printNames(someNames)
The corrected version works because the function receives the exact type it expects.
7. Best Practices
Use slices for short-term, efficient views
If you only need to inspect or process part of a collection briefly, a slice is a good choice because it avoids an immediate copy.
let readings = [5, 10, 15, 20, 25]
let recent = readings.suffix(2)
for value in recent {
print(value)
}
This is efficient and clear when the slice is used immediately.
Convert to Array when you need independent storage or zero-based indexing
If later code expects an array, or if you need predictable zero-based indexes, convert the slice explicitly.
let items = ["pen", "book", "lamp", "desk"]
let selection = Array(items[1...2])
print(selection[0])
This makes the collection behave like a normal standalone array.
Prefer iteration over manual index assumptions
Many slice-related bugs happen because code assumes indexes restart at zero. Iteration usually avoids that issue entirely.
let data = [100, 200, 300, 400]
let segment = data[1...3]
for value in segment {
print(value)
}
This approach is often safer and easier to read than manual numeric indexing.
8. Limitations and Edge Cases
- An ArraySlice is not automatically interchangeable with Array. You may need Array(slice).
- Slice indexes usually preserve the original collection’s positions, which can surprise developers expecting zero-based access.
- Keeping a small slice can keep a large original collection’s storage alive longer than intended.
- Methods like prefix, suffix, dropFirst, and range subscripting often return subsequences, not full collections.
- Different collection types can have different subsequence types. ArraySlice is common for arrays, but SubSequence is a broader collection concept.
- If a range is outside valid bounds, your program can crash at runtime due to invalid indexing.
9. Practical Mini Project
This mini project shows a realistic use of slices. We will take the latest three messages from a chat history, print them, and then convert them to an array for a function that expects an array.
func displayMessages(_ messages: [String]) {
for message in messages {
print("- \(message)")
}
}
let chatHistory = [
"Hello",
"How are you?",
"I am fine",
"Are you free later?",
"Yes, after 6"
]
let latestMessagesSlice = chatHistory.suffix(3)
print("Latest messages as a slice:")
for message in latestMessagesSlice {
print(message)
}
let latestMessagesArray = Array(latestMessagesSlice)
print("Formatted output:")
displayMessages(latestMessagesArray)
This example shows both common patterns. First, use the slice directly for short-term iteration. Then convert it to an array when another API expects array input.
10. Key Points
- A slice is a view into part of a collection rather than a brand-new copied collection.
- A subsequence is the general type concept for that partial collection result.
- For arrays, slices are usually represented as ArraySlice.
- ArraySlice keeps the original indexes instead of restarting at zero.
- Methods like prefix, suffix, and dropFirst often return slices.
- Convert a slice with Array(slice) when you need an actual array.
- Long-term storage of a small slice can unintentionally keep a large collection in memory.
11. Practice Exercise
Try this exercise to confirm that you understand both slices and array conversion.
- Create an array of seven integers.
- Take a slice containing the middle three elements.
- Print the slice directly.
- Convert the slice to an array.
- Print the first element of the new array using index 0.
Expected output: You should see the three middle values printed as a slice, followed by the first value from the converted array.
Hint: Use a range such as 2...4 and then call Array(...) on the slice.
let numbers = [5, 10, 15, 20, 25, 30, 35]
let middleSlice = numbers[2...4]
print(middleSlice)
let middleArray = Array(middleSlice)
print(middleArray[0])
12. Final Summary
Swift slices and subsequences let you work with part of a collection efficiently. For arrays, that usually means working with ArraySlice, which shares storage with the original array and preserves the original indexes. That makes slices fast and useful for short-term operations such as taking a prefix, suffix, or range.
The most important thing to remember is that a slice is not the same as an array. It may not start at index 0, it may not match APIs expecting [Element], and it may keep the original collection alive in memory. When you need independent storage or array-style indexing, convert the slice explicitly with Array(slice).
A good next step is to study Swift collection indexes and range operators in more detail, because those concepts make slices much easier to use correctly and confidently.