Skip to content
AgileLogics
7 min read

Closures

Prerequisites: Parameters and argument labels

A closure is a function without a name, written inline, that you can store in a variable or pass to another function. You've already used them: { $0 > 20 } in the Array lessons is a closure.

Real-world analogy: a closure is an instruction slip you hand to someone along with a job — "for each parcel, check the weight like this". They decide when to follow it.

Full syntax

let double = { (n: Int) -> Int in
    n * 2
}

print(double(21))
42

The parameters and return type come first, then in, then the body.

Passing a closure to a function

let names = ["Linus", "ada", "Grace"]

let sorted1 = names.sorted(by: { (a: String, b: String) -> Bool in
    a.lowercased() < b.lowercased()
})
print(sorted1)
["ada", "Grace", "Linus"]

Shorter and shorter

Swift lets you trim a closure down step by step when the meaning stays clear. All of these are the same:

let names = ["Linus", "ada", "Grace"]

// Types inferred from context
let a = names.sorted(by: { x, y in x.lowercased() < y.lowercased() })

// Shorthand argument names: $0, $1, ...
let b = names.sorted(by: { $0.lowercased() < $1.lowercased() })

// Trailing closure: the closure goes after the parentheses
let c = names.sorted { $0.lowercased() < $1.lowercased() }

print(a == b && b == c)
true

A trailing closure — writing the closure after the call's parentheses — is the style you'll see most, especially in SwiftUI.

Writing a function that takes a closure

The parameter's type describes the closure: (Int) -> Void means "takes an Int, returns nothing".

func repeatTask(times: Int, task: (Int) -> Void) {
    for i in 1...times {
        task(i)
    }
}

repeatTask(times: 3) { pass in
    print("Pass \(pass)")
}
Pass 1
Pass 2
Pass 3

Capturing values

Closures capture variables from the surrounding code and keep them alive:

func makeCounter() -> () -> Int {
    var count = 0
    return {
        count += 1
        return count
    }
}

let next = makeCounter()
_ = next()
_ = next()
print(next())
3

count would normally disappear when makeCounter returns, but the closure keeps it alive. (_ = ignores a return value you don't need.)

Common mistakes

  • Over-shortening. $0 and $1 are great for one-liners, but name the parameters when a closure is longer than a line or two.
  • Forgetting in after the parameter list in the full syntax.

Practice

Sort ["banana", "fig", "apple"] by word length, shortest first, using a trailing closure.

Show solution
let words = ["banana", "fig", "apple"]
print(words.sorted { $0.count < $1.count })
["fig", "apple", "banana"]