0Pricing
Swift Academy · Lesson

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, and min read the property through a key path inside their comparison closures.
  • Generic helpers that take a KeyPath turn 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

  1. KeyPath Basics
  2. Using KeyPaths in Collections
  3. Writable and Reference KeyPaths
  4. @dynamicMemberLookup
← Back to Swift Academy