Closures & Trailing Closures in Swift
- danielardelsmith
- Feb 18
- 8 min read

Trailing closures are a common and extremely powerful feature in Swift, making code more readable when calling functions. To understand how a trailing closure can be so useful, let's make sure we understand what a regular closure is within a function.
But first, closures...
Let's start off with understanding closures. When defining a function, we can specify parameters to allow the function to receive data and operate on it.
Here's a simple function that will repeatedly print a phrase depending on the amount of iterations the caller gives:
func repeatAction(times: Int) {
for _ in 1...times {
print("Hello, Swift!")
}
}
repeatAction(times: 3) // prints "Hello, Swift!" 3 times
However, this function is now restricted in that it can only print the phrase "Hello, Swift!" What if we want a function that can repeat anything? What if we want to print "Goodbye, Swift!" instead? You might think, "We'll just define another function that prints something else!"
Once you've defined another function, I ask you, "Yeah, but now I want a function to print "This is getting out of hand!" 3 times instead." You can quickly see how we are re-writing a lot of code here and, when programming, we want to avoid writing redundant code!
This is where closures come in. Instead of defining a function that hard-codes the action we want to repeat, we can instead define a function that accepts a block of code (a closure) as a parameter and repeats that instead. When we call the function, we can give it the closure that we want it to run.
Let's have a look:
func repeatAction(times: Int, action: () -> Void) {
for _ in 1...times {
action()
}
}
Here, we have specified a second parameter called 'action' and we have specified the data type. This might look scary at first, but it is really just the shorthand for a function declaration.
Remember how a normal function can accept parameters and return values. When telling Swift about this 'action' closure parameter, we must define what the block of code can accept and what it might return.
In our case, we have defined a closure that takes no input parameters (shown by the empty parentheses) and returns no value (denoted by 'Void'). So, when calling the function, it expects a closure that takes no parameters and returns no values. Instead, it will just perform some self-contained code.
Within the function, instead of writing a specific print statement, we ask it to repeat the closure that was provided, called 'action.'
Now, when we call the function we can tell it to repeat any code we want!
repeatAction(times: 3, action: { your code here })
// in practice:
repeatAction(times: 3, action: {
print("Hello from inside a closure!")
})
repeatAction(times: 3, action: {
print("Hello once again!")
})
Closures With Parameters
Let's have a look at an example where a closure expects parameters and can return something. For the input parameters, instead of leaving the parentheses empty, we can provide the parameters that the closure should expect similar to how we would define a regular function.
Let's define a function that takes an array and a closure that sorts the array. It will use the closure that the caller specifies to sort the array then return it to the caller!
func sortArray(array: [Int], method: ([Int]) -> [Int]) -> [Int] {
return method(array)
}
Let's look at the 'method' parameter:
method: ([Int]) -> [Int]
^ ^
input output
We are specifying that the closure we pass the function must take in an input array of integers and similary must return an array of integers. The sort array function will call 'method' within itself to sort the array and will return the result to the caller.
Let's try it out:
var myArray = [3, 4, 1, 2]
let newArray = sortArray(array: myArray, method: { unsortedArray in
// your code to sort the array here
return sorted
})
print(newArray)
Notice the extra 'unsortedArray in' when we first open the closure. Remember, the 'method' parameter in our function is defined to take a closure that accepts an array of integers [Int]. When the function calls 'method(array)', Swift passes 'array' into our closure, and we access it using the name 'unsortedArray'. This lets us work directly with the input inside our custom sorting logic.
We can now reuse the 'sortArray()' function anywhere, and specify how to sort the array at each call. For example, at one call we might sort in ascending order whereas in another call we might want to sort in descending order.
That's it, isn't it?
You've successfully written a closure! But wait, what about a trailing closure? Going back, look at how we've called the 'repeatAction()' and 'sortArray()' function above. Specifically, look at how we've mixed in simple parameters with our closure parameter in the same line. We've also had to specify the parameter name for the closure in the call, as well as extend the close parentheses to a few lines below the function call.
This isn't too hard to read when scanning over this simple code, but once we start to create more complex functions with more parameters and larger closures (and even multiple closures!) it can get pretty unwieldy to read. Instead, we want a neat way to separate the simple parameters from the closures we pass in.
Trailing Closures?
Here's the thing: in the examples above, we've already defined the functions in a way that allows for a neater function call. As long as the closure parameter is the last item within the parameter list we can omit the name of the parameter, then place the closure directly after the function call. Take a look:
// normal closure
repeatAction(times: 3, action: {
print("Hello from a normal closure")
})
// trailing closure
repeatAction(times: 3) {
print("Hello from a trailing closure")
}
See how the closure is completely separate from the simple parameter? We've also completely omitted the 'action' parameter name from the function call, and we've closed off the parentheses on the call line then provided the closure after! It's subtle, I know, but this is a relatively simple function. When building more complex functions, it becomes harder visually separate the simple parameters from closures, so instead we use trailing closures!
Trailing Closure for our sortArray() Method
Let's look at a trailing closure that accepts parameters and returns values:
var myArray = [3, 4, 1, 2]
// normal closure
let newArray = sortArray(array: myArray, method: { unsortedArray in
// your code to sort the array here
return sorted
})
// trailing closure
let newArray = sortArray(array: myArray) { unsortedArray in
// your code to sort the array here
return sorted
}
Notice how the code reads a lot cleaner? That's a trailing closure! The final parameter name in the function call can be omitted, we can close the parentheses of the function call, then we can place the separate method of sorting in a closure directly after it.
Trailing Closures in Action
Who'd've guessed it (yes that's a real word), Swift makes use of its own features. There are a variety of objects within Swift that possess functions accepting trailing closures. Let's have a look at a few.
Iterating Over Items in an Array
Say we have an array of names:
let names = ["Chris", "Alice", "Penelope"]
We want to iterate over each name in the array and greet each person. We may employ a simple for loop. However, Arrays in Swift already have a built in function that uses a trailing closure. The function automatically iterates over each item within an array and performs the closure we provide on each item. Have a look:
names.forEach { name in
print("Hello, \(name).")
}
// prints:
// Hello, Chris.
// Hello, Alice.
// Hello, Penelope.
And we can imagine the function definition to look like:
struct Array {
var arrayContents: [String]
// ... other array stuff
func forEach(action: (String) -> Void) {
for item in arrayContents {
action(item)
}
}
// .. other array stuff
}
// This is massively simplified. An array probably
// doesn't look like this really, but it exemplifies
// how a trailing closure is handled
Notice how we don't even have to use () at all when calling the function? That's because the only parameter in the function definition is the closure! We can omit the parentheses. This improves readability of the code, allowing us to quickly identify that we are iterating over each item in 'name' and performing some logic with it.
SwiftUI Button
Another great example is a Button View in SwiftUI. A button can be declared to show a piece of text and run a closure when tapped. Within our View, we would define:
Button("Tap Me") {
print("I've been tapped!")
}
Now, if you're unfamiliar with SwiftUI, this syntax may look confusing. To explain simply, we are declaring a button object to be instanced within a view with the text "Tap Me", as well as the method to run when it's tapped.
When we instance a button like this, behind the scenes it is calling a function called 'init' which accepts parameters of both the text do display on the button and a trailing closure for the code to run when it's tapped. We can imagine the 'init' function to look like this:
struct Button: View {
@State var textToDisplay: String
var someMethod: () -> Void // to store the closure for later
// this method is called when we instance a button.
// it constructs and returns a reference to a button
init(_ text: String, action: @escaping () -> Void) -> Button {
textToDisplay = text
someMethod = action
// some other stuff
}
// this is where the visual button is constructed
var body: some View {
Text(textToDisplay)
.onTapGesture { // another trailing closure in action!
someMethod() // called when tapped
}
}
}
// again, this is simplified for educational purposes
In the struct above, the 'init' method is called when we instance a Button (yes, 'init' methods don't need the 'func' keyword, but they are still a function!). Within the 'init' function, the button is storing the closure for later use when the button is tapped. Within the body of the View, the closure we provided is called when the user performs a tap gesture on the text.
You may notice the extra '@escaping' just after we define the parameter name. We use this to tell Swift that the closure will be used after the function that receives it has finished executing. Our closure is being stored for later use (when the user taps). This makes sure Swift does not discard the closure after 'init' has finished running.
That's it, for real!
You've covered the basics of closures and trailing closures in Swift. To recap, closures are self-contained blocks of code that can accept parameters and return values, and trailing closures are a form of syntax implemented to improve the readability of code by separating the simple parameters of a function call from the closures. The remove the need for extra parameter names and distant closing parentheses, and maybe even the entire parentheses altogether.
Now, go practice trailing some closures...
Found this guide useful? Share it with your Swift developer friends on social media!
Want to see more of my work? Check out my Apple Watch app, FlexTracker. A useful shift tracker for multiple employments right on your wrist!