Other functions with closures
Learning intentions
In this lesson, you will explain what closures are and how they connect to array helpers. Recognise closure shorthand syntax and how to count closure arguments. Use safer helpers like compactMap, allSatisfy, and search methods to avoid manual loops.
Watch the overview video: Map, filter, and reduce in Swift
Closures in Swift
A closure is a function you can store in a variable or pass into another function. Array helpers like map, filter, and reduce take closures to do the work. If you need the basics of those helpers, see the previous page.
How to spot a closure parameter
Swift uses the arrow syntax (Input) -> Output to describe a closure type. If a function has a parameter like (Element) -> T, it expects a closure that takes one value and returns one value. These Apple docs show the real signatures: Array.map(_:), Array.filter(_:),
Shorthand arguments ($0, $1)
Swift lets you skip parameter names and use $0, $1, and so on. $0 is the first argument, $1 is the second. The number of arguments comes from the closure type in the function signature.
let numbers = [1, 2, 3, 4, 5]
let squares = numbers.map { $0 * $0 }
let evens = numbers.filter { $0 % 2 == 0 }
let sum = numbers.reduce(0) { $0 + $1 }
Safer array helpers
These helpers remove nil values, check conditions, and search safely without manual loops. Docs: Array.compactMap(_:), Sequence.allSatisfy(_:), Sequence.first(where:), Collection.firstIndex(where:), BidirectionalCollection.last(where:), Sequence.min(by:),
compactMap: remove nil values
compactMap runs a transform and drops any nil results. This avoids force-unwrapping and extra checks.
let rawScores = ["42", "x", "100", "", "65"]
let scores = rawScores.compactMap { Int($0) }
print(scores) // [42, 100, 65]
allSatisfy: check every element
allSatisfy returns true only if every element matches the condition. This is clearer than tracking a manual isValid flag.
let ages = [13, 14, 15, 16]
let allTeenagers = ages.allSatisfy { $0 >= 13 && $0 <= 19 }
print(allTeenagers) // true
first(where:) and firstIndex(where:)
first(where:) returns the first element that matches. firstIndex(where:) returns the position of the first match. Both return optionals,
let names = ["Aroha", "Moana", "Hana", "Pita"]
let firstLongName = names.first { $0.count >= 5 }
let firstLongIndex = names.firstIndex { $0.count >= 5 }
print(firstLongName ?? "none")
print(firstLongIndex ?? -1)
last(where:)
last(where:) finds the last element that matches a condition. This avoids reversing arrays or writing a backwards loop.
let temperatures = [18, 21, 19, 23, 17, 22]
let lastWarmDay = temperatures.last { $0 >= 20 }
print(lastWarmDay ?? 0) // 22
min(by:) and max(by:)
These find the smallest or largest element using a custom rule. They are safer than manual comparisons because the rules are explicit.
let cities = ["Auckland", "Wellington", "Christchurch"]
let shortest = cities.min { $0.count < $1.count }
let longest = cities.max { $0.count < $1.count }
print(shortest ?? "")
print(longest ?? "")
Task A: hidden numbers
You are given an array containing animal names and number strings.
Use compactMap to keep only the numbers and convert them to Int, then print the result.
Then, as an alternative check, use allSatisfy on the original array to test if every item is a number string.
let mixed = ["cat", "7", "owl", "15", "dog", "3"]
Task B: the midnight filter
You are given a list of sightings with a name and a danger score.
Use filter to keep only sightings where the name starts with "m" or "w".
Use map to extract the scores, then reduce to calculate the total.
Finally, use min(by:) and max(by:) to find the lowest and highest scores in the filtered set.
let sightings = [
(name: "moth", score: 3),
(name: "wolf", score: 9),
(name: "raven", score: 4),
(name: "mist", score: 7),
(name: "wisp", score: 2)
]
Task C: safe input check
Create a function called accepts that takes a String and a closure parameter called isValid.
The closure should have the type (String) -> Bool and decide if the input passes.
Return true or false based on the closure.
Test your function with a predicate that only allows lowercase words and a predicate that only allows strings longer than 8 characters.
func accepts(_ input: String, isValid: (String) -> Bool) -> Bool {
return isValid(input)
}
let sample = "moonlight"
Task D: the haunted archive
The archive has wings, each wing has rooms, each room has shelves, and each shelf holds words.
Find the first word that starts with "e" on the last shelf that contains an "e" word in the last room that contains any 4-letter word in the last wing that contains any word starting with "e".
Use first(where:) and last(where:) at each step, and print the final word.
let archive = [
[
[["candle", "dust"], ["mirror", "ash"]],
[["whisper", "shadow"], ["clock", "veil"]]
],
[
[["stone", "key"], ["relic", "name"]],
[["cipher", "bone"], ["ember", "seal"]]
],
[
[["feather", "ink"], ["glow", "eclipse"]],
[["riddle", "echo"], ["ember", "glyph"]]
]
]
Summary
Closures let you pass functions into other functions. Shorthand arguments like $0 and $1 match the closure parameter count. Helpers like compactMap, allSatisfy, first, last, min, and max avoid risky manual loops.
Extension for Super Players!
Create your own data set and use map, filter, and reduce to answer a new question. Explain what you are trying to measure,