Skip to content
Educora
Intermediate18 min6 / 14

Functions and closures

Split code into functions with argument labels, default values and return values. Then learn closures, their short forms and trailing closures.

Check yourself
In this lesson you will learn
  • Write and call functions with parameters and return values
  • Use argument labels, _ and default parameter values
  • Write closures, shorten them and recognise trailing closure syntax

Imagine you calculate a ticket price in ten different places in your app. When the rules change, you have to fix ten places — and you will surely forget one. The answer is a function: you write the code once, give it a name and call it wherever you need it. You have already used ready-made functions such as print, sorted() and map — now you will write your own.

Declaring a function

A function starts with the keyword func. The parameters are written in parentheses together with their types, and the type of the returned value comes after ->. return sends the result back. If the body of the function is a single expression, you can leave out the word return.

Swift
func greet(name: String) -> String {
    return "Hello, \(name)!"
}

func square(_ x: Int) -> Int {
    x * x
}

print(greet(name: "Aysel"))
print(square(7))
print(square(square(2)))
Expected output
Hello, Aysel!
49
16

Argument labels and default values

In Swift every parameter can have two names, so that a call reads like a sentence: an argument label (written when calling) and a parameter name (used inside the function). The function travel(from start: String, to end: String) is called as travel(from: "Baku", to: "Shaki"). If you don't want a label, write _ instead — that is exactly how square(7) works.

A parameter can have a default value: if you leave it out when calling, that value is used. Below, a ticket costs 5 manat for children under 12, 7.5 manat for students and 10 manat for everyone else.

Swift
func travel(from start: String, to end: String) {
    print("Travelling from \(start) to \(end)")
}

func ticketPrice(for age: Int, isStudent: Bool = false) -> Double {
    if age < 12 {
        return 5.0
    } else if isStudent {
        return 7.5
    }
    return 10.0
}

travel(from: "Baku", to: "Shaki")
print(ticketPrice(for: 8))
print(ticketPrice(for: 20))
print(ticketPrice(for: 20, isStudent: true))
Expected output
Travelling from Baku to Shaki
5.0
10.0
7.5

Closures

Definition
Closure

A block of code without a name that can be stored in a variable and passed to another function. It is written in curly braces: first the parameters and return type, then the word in, then the body.

Swift lets you shorten closures step by step: it infers the types from the context, does not require return for a single expression and lets you refer to the parameters as $0 and $1. Below, the same sort is written four times, each one shorter:

Swift
let double = { (x: Int) -> Int in
    return x * 2
}
print(double(5))

let numbers = [5, 2, 9, 1]
print(numbers.sorted(by: { (a: Int, b: Int) -> Bool in
    return a > b
}))
print(numbers.sorted(by: { a, b in a > b }))
print(numbers.sorted { $0 > $1 })
print(numbers.sorted(by: >))
Expected output
10
[9, 5, 2, 1]
[9, 5, 2, 1]
[9, 5, 2, 1]
[9, 5, 2, 1]

Trailing closures

If a closure is the last argument of a function, you can write it after the parentheses — this is called a trailing closure. numbers.sorted { $0 > $1 } and prices.map { $0 * 2 } are written exactly like that. You can also write your own function that takes a closure; its parameter has a function type such as () -> Void.

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

repeatTask(times: 3) {
    print("Hip hip hooray!")
}

func apply(_ value: Int, _ operation: (Int) -> Int) -> Int {
    operation(value)
}

func triple(_ x: Int) -> Int {
    x * 3
}

print(apply(4) { $0 + 10 })
print(apply(4, triple))
Expected output
Hip hip hooray!
Hip hip hooray!
Hip hip hooray!
14
12
You can pass a function either a closure or another named function.

Closures remember the variables around the place where they were created (this is called capturing). The makeCounter below returns a counter that grows by one on every call: the variable count keeps living inside the closure even after the function has finished.

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

let counter = makeCounter()
print(counter())
print(counter())
print(counter())
Expected output
1
2
3

Key points

  • A function is declared as func name(parameter: Type) -> ReturnType { ... }.
  • The argument label is used in the call and the parameter name inside the function; _ removes the label.
  • A parameter with a default value can be left out of the call.
  • A closure is an unnamed function; $0 and $1 are its parameters.
  • A closure that is the last argument can go after the parentheses; closures remember the variables around them.

Check yourself

10 questions. Every correct answer earns XP.

1 / 10
Which is a correct call of func travel(from start: String, to end: String)?