Section 1 · Swift the Language
Functions and Closures
18 min3 min readFill in the blanks
Lesson at a glance
Write functions with clear argument labels and default values, then replace hand-written loops with closures passed to filter, map, reduce and sorted.
- 1Functions that read like sentences
- 2Closures: functions without names
- 3filter, map, reduce, sorted
- 4Closures capture their surroundings
No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.
The for loops from the last lesson work, but they make you read five lines to learn one fact, such as “the total spent on food”. Functions give a piece of logic a name. Closures let you hand a small piece of logic to someone else, like “keep the ones over 50”, and Swift’s collection methods are built around them. Together they turn most loops in Pocket Budget into one readable line.
For the examples, here is a minimal expense type. You’ll learn what struct really means in the next section; for now, read it as “a value with a title, an amount and a category”.
import Foundation
struct Expense {
let title: String
let amount: Decimal
let category: String
}
let expenses = [
Expense(title: "Groceries", amount: 64.2, category: "Food"),
Expense(title: "Bus pass", amount: 45, category: "Transport"),
Expense(title: "Coffee", amount: 4.5, category: "Food"),
Expense(title: "Concert", amount: 80, category: "Fun"),
]Functions that read like sentences
func total(of expenses: [Expense], in category: String? = nil) -> Decimal {
var sum: Decimal = 0
for expense in expenses where category == nil || expense.category == category {
sum += expense.amount
}
return sum
}
total(of: expenses) // 193.7
total(of: expenses, in: "Food") // 68.7Each parameter has two names. The argument label (of, in) is what callers write; the parameter name (expenses, category) is what the body uses. That split is why Swift call sites read like English: “total of expenses in Food”. The = nil gives category a default value, so callers can leave it out. Write _ as the label when it adds nothing, as in print(_:).
The -> Decimal declares the return type. A function that returns nothing omits the arrow.
Closures: functions without names
A closure is a block of code you can store in a constant or pass as an argument. Here is a closure that decides whether an expense is large:
let isLarge: (Expense) -> Bool = { expense in
expense.amount > 50
}
isLarge(expenses[0]) // trueThe type (Expense) -> Bool reads “takes an Expense, returns a Bool”. Parameters go before in, the body after it. A single-expression body returns its value without writing return.
Now pass logic like that to filter. These three lines are the same call, progressively shortened:
let large1 = expenses.filter({ (expense: Expense) -> Bool in
return expense.amount > 50
})
let large2 = expenses.filter { expense in expense.amount > 50 }
let large3 = expenses.filter { $0.amount > 50 }When a closure is the last argument, you can write it after the parentheses (trailing closure syntax) and drop empty parentheses entirely. Swift already knows the types from filter’s signature, so you can drop those too. $0 is the first parameter, $1 the second.
filter, map, reduce, sorted
These four cover most of what you do with collections:
let food = expenses.filter { $0.category == "Food" } // keep matching expenses
let titles = food.map { $0.title } // ["Groceries", "Coffee"]
let foodTotal = food.reduce(0) { $0 + $1.amount } // 68.7
let biggestFirst = expenses.sorted { $0.amount > $1.amount } // Concert, Groceries, ...reduce starts from an initial value (0) and folds each element into a running result: $0 is the total so far and $1 is the next expense. sorted(by:) asks your closure “should $0 come before $1?”.
Chain them, and the pipeline reads in the order it runs:
let foodSpend = expenses
.filter { $0.category == "Food" }
.map(\.amount)
.reduce(0, +)\.amount is a key path, a shorthand for { $0.amount }, and + is itself a function that takes two values and returns their sum, so you can pass it straight to reduce.
Closures capture their surroundings
A closure can use variables from the scope where it was created, and it keeps them alive:
func makeLimitCheck(limit: Decimal) -> (Decimal) -> Bool {
return { amount in amount > limit }
}
let overFoodLimit = makeLimitCheck(limit: 60)
overFoodLimit(64.2) // truemakeLimitCheck has returned, yet the closure still knows limit is 60. When a function stores a closure parameter to call later, instead of calling it before returning, Swift makes you mark that parameter @escaping. The keyword is a warning label for readers: this closure outlives the call, so whatever it captures stays alive too.
This capturing is what lets SwiftUI buttons run code that refers to your view’s data long after the view was built. It has one sharp edge, a reference cycle when a class and a closure capture each other, which you’ll meet with classes in the next lesson.
You can now name logic, pass it around and replace loops with pipelines. That finishes the core language. The next section is about modelling: deciding whether Pocket Budget’s data should be structs, classes or enums, and why that choice changes how your app behaves.
Key takeaways
- Argument labels make call sites read like sentences; use
_only when the label would add nothing. - A closure is a function without a name, and trailing closure syntax lets you write it after the call’s parentheses.
filterkeeps elements,maptransforms them,reducecombines them into one value, andsorted(by:)returns a new ordered array.- A closure captures the variables it uses, so it can carry context such as a limit to wherever it’s called later.