Using KeyPaths in Collections
Sort and map collections by key path.
Using KeyPaths in Collections is a free Swift Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Key Paths Shine in Collections
Collection operations like map, sorted, and filter often just read one property of each element. Key paths let you express that intent without writing a closure.
Swift accepts a key path anywhere a single-argument function returning a value is expected, because a key path can be used as a function from Root to Value.
struct Person {
let name: String
let age: Int
}
let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let names = people.map(\.name)
print(names)map with Key Path Shorthand
Passing map(\.property) is equivalent to writing a closure that returns that property, but shorter and clearer. The leading backslash and dot let the compiler infer the element type.
Compare the two forms below; they produce the same result.
struct Person {
let name: String
let age: Int
}
let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let closureForm = people.map { $0.age }
let keyPathForm = people.map(\.age)
print(closureForm)
print(keyPathForm)sorted(by:) With a Key Path Comparison
To sort by a property, you still write a small comparison closure, but you can read the property through a key path inside it for clarity.
Here we sort people from youngest to oldest by comparing their age values.
struct Person {
let name: String
let age: Int
}
let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let path = \Person.age
let sorted = people.sorted { $0[keyPath: path] < $1[keyPath: path] }
print(sorted.map(\.name))A Reusable Sort Helper
You can write a generic helper that sorts any array by any comparable property, taking the property as a key path argument. This turns sorting by a field into a one-liner across your codebase.
The constraint T: Comparable ensures the property can be ordered.
func sorted<E, T: Comparable>(_ array: [E], by path: KeyPath<E, T>) -> [E] {
array.sorted { $0[keyPath: path] < $1[keyPath: path] }
}
struct Person { let name: String; let age: Int }
let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
print(sorted(people, by: \.age).map(\.name))Filtering Through a Key Path
For filter you need a predicate that returns a Bool, so a bare property key path only works directly when the property is already a Bool.
Here each task has an isDone flag, and we filter on it using the key path shorthand.
struct Task {
let title: String
let isDone: Bool
}
let tasks = [Task(title: "A", isDone: true), Task(title: "B", isDone: false)]
let finished = tasks.filter(\.isDone)
print(finished.map(\.title))Filtering on a Non-Bool Property
When you filter on a non-Bool property, read it through a key path inside a closure and compare it. A generic helper can capture this pattern for any equatable property.
Here we keep only the people whose city matches a target.
struct Person { let name: String; let city: String }
func filter<E, T: Equatable>(_ array: [E], _ path: KeyPath<E, T>, equals value: T) -> [E] {
array.filter { $0[keyPath: path] == value }
}
let people = [Person(name: "Ana", city: "Oslo"), Person(name: "Ben", city: "Rome")]
print(filter(people, \.city, equals: "Oslo").map(\.name))max and min by Key Path
The max(by:) and min(by:) methods take a comparison closure just like sorted. Read the property through a key path to find the element with the largest or smallest value.
Here we find the oldest person.
struct Person { let name: String; let age: Int }
let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25), Person(name: "Cy", age: 41)]
let path = \Person.age
if let oldest = people.max(by: { $0[keyPath: path] < $1[keyPath: path] }) {
print(oldest.name)
}Summing a Property With reduce
You can combine map(\.property) with reduce to total a numeric field. First project each element to the number you want, then add them up.
Here we sum all ages in one pipeline.
struct Person { let name: String; let age: Int }
let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let totalAge = people.map(\.age).reduce(0, +)
print(totalAge)Grouping by a Key Path
Dictionary(grouping:by:) buckets elements by the result of a closure. Read a property through a key path to group by that field.
Here people are grouped by city into a dictionary of arrays.
struct Person { let name: String; let city: String }
let people = [
Person(name: "Ana", city: "Oslo"),
Person(name: "Ben", city: "Rome"),
Person(name: "Cy", city: "Oslo")
]
let path = \Person.city
let byCity = Dictionary(grouping: people, by: { $0[keyPath: path] })
print(byCity["Oslo"]?.map(\.name) ?? [])Multi-Field Sorting
To sort by one field and break ties with another, compare the primary key path first and fall back to the secondary when the primaries are equal.
Here we sort by age, then by name for equal ages.
struct Person { let name: String; let age: Int }
let people = [
Person(name: "Zoe", age: 30),
Person(name: "Ana", age: 30),
Person(name: "Ben", age: 25)
]
let sorted = people.sorted {
if $0[keyPath: \Person.age] != $1[keyPath: \Person.age] {
return $0.age < $1.age
}
return $0.name < $1.name
}
print(sorted.map(\.name))Why Key Paths Beat Closures Here
Using key paths in collection calls reduces boilerplate, removes the chance of a typo in the closure body, and makes the intent obvious: you are projecting a single field.
For complex transforms a closure is still the right tool, but for a plain property read, \.property is the clearest choice.
struct Product { let name: String; let price: Double }
let products = [Product(name: "Pen", price: 2), Product(name: "Pad", price: 5)]
let names = products.map(\.name)
let prices = products.map(\.price)
print(names)
print(prices)Quick Check: Key Path in map
Pick the concise key path form for extracting one property from each element.
Recap: Key Paths in Collections
You saw how key paths make collection code concise:
map(\.property)projects a single field with no closure.filter(\.boolProperty)works directly for Bool fields; use a closure with[keyPath:]for others.- Sorting,
max, andminread the property through a key path inside their comparison closures. - Generic helpers that take a
KeyPathturn sorting and filtering by a field into reusable one-liners.
Next you will learn writable key paths that let you set values, not just read them.
struct Person { let name: String; let age: Int }
let people = [Person(name: "Ana", age: 30), Person(name: "Ben", age: 25)]
let youngestFirst = people.sorted { $0[keyPath: \Person.age] < $1[keyPath: \Person.age] }
print(youngestFirst.map(\.name))Frequently asked questions
Is the “Using KeyPaths in Collections” lesson free?
Yes — the full text of “Using KeyPaths in Collections” is free to read here on the web, and the Swift Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Swift Academy course, upgrade to CoddyKit PRO.
What will I learn in “Using KeyPaths in Collections”?
Sort and map collections by key path. You practise Swift Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Swift Academy?
No prior experience is required. Swift Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Using KeyPaths in Collections” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Swift Academy lesson?
Yes. Every Swift Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- KeyPath Basics
- Using KeyPaths in Collections
- Writable and Reference KeyPaths
- @dynamicMemberLookup