The Ultimate Guide To Closures In Swift

Written by Reinder de Vries on May 20 2018 in App Development

The Ultimate Guide To Closures In Swift

If you had a tough time understanding optionals, then you’ll probably find the prospect of mastering closures even more terrifying! Don’t worry though, they are more harmless than they look. And closures are useful, too!

This article dives into Swift closures. Closures are blocks of code that you can pass around in your code, as if you assign a function to a variable. Mastering closures is a crucial aspect of learning iOS development.

Here’s what you’ll learn:

  • How closures work and how you can use them
  • The syntax for declaring and calling closures
  • How to compose and decompose a closure’s type
  • What “closing over” means, and how to use a capture list
  • How to use a closure effectively as a completion handler

Ready? Let’s go.

  1. How A Closure Works
  2. How Closure Types Work
  3. Closures And Type Inference
  4. Capturing And The Capture List
  5. Closures In Action: The Completion Handler
  6. Further Reading

You’ll have to forgive me for the cheesy tropical island image at the top of this article. For starters, it’s really hard to find a visual representation of an abstract concept like a closure. On top of that, finally “getting” closures is kinda like arriving at a tropical destination. And by the way, you’ll definitely need a happy place to endure the journey towards the mastery of closures… ;-)

How A Closure Works

Let’s look at Apple’s official description of a closure:

Closures are self-contained blocks of functionality that can be passed around and used in your code.

Said differently, a closure is a block of code that you can assign to a variable. You can then pass it around in your code, for instance to another function. The function then calls the closure and executes its code, as if the closure is an ordinary function.

As you know, variables store information in your Swift code, and functions can execute tasks. With closures, you put a function’s code in a variable, pass it around, and execute its code somewhere else.

Let’s look at an analogy:

  • Bob gives Alice an instruction: wave your hands. Alice hears the instruction, and waves her hands. The hand-waving is a function.
  • Alice writes her age on a piece of paper, and gives it to Bob. The piece of paper is a variable.
  • Bob writes “Wave your hands!” on a piece of paper and gives it to Alice. Alice reads the instruction on the piece of paper, and waves her hands. The instruction, as passed on the piece of paper, is a closure.

Makes sense, right? Let’s write a closure in Swift!

let birthday = {
print("Happy birthday!")


Here’s what happens in that code:

  • On the first line, you’re defining a closure and assigning it to birthday. The closure is the stuff between the squiggly brackets { and }. See how it’s similar to a function declaration? Note that the closure is assigned to birthday with the assignment operator =.
  • On the last line, the closure is called. It’s executed by calling birthday(), the name of the constant birthday with parentheses (). This is similar to calling a function.

At this point, the type of birthday, and the type of the closure, is () -> (). More on closure types later.

Next, let’s add a parameter to the closure. Just like functions, closures can have parameters.

let birthday:(String) -> () = { name in
print("Happy birthday, \(name)!")


Alright, that’s getting more complicated. Let’s decompose that code bit by bit. Here’s what happens:

  • Just like before, we’re declaring the closure on the first line, then assign it to the constant birthday, and call the closure on the last line.
  • The closure now has one parameter of type String. It’s declared within the closure type (String) -> ().
  • You can then use the parameter name within the closure. When calling the closure, you provide a value for the parameter.

What do you make of that? In essence, there are three things that matter here:

  • The closure type (String) -> ()
  • The closure expression { name in ... }
  • The closure call birthday(...)

Unlike Swift functions, the parameters of a closure aren’t named. When you declare a closure you can specify the types of parameters it has, such as String in the above example, but you don’t specify a parameter name.

In the closure expression, the { name in ... part, you assign a local variable name to the first parameter of the closure. You could have named it anything you wanted.

In fact, you could have left it out and used the $0 shorthand! Like this:

let birthday:(String) -> () = {
    print("Happy birthday, \($0)!")

In the above code, the closure birthday has one parameter. Inside the closure, the shorthand $0 is used to reference the value of that parameter.

Here’s what you’ve learned so far:

  • A closure is a block of code that you can pass around in your code
  • Closures can have zero, one or more parameters
  • Every closure has a type, including any closure parameters

Let’s take a closer look at the type of a closure, in the next section.

Learn how to build iOS apps

Get started with iOS 12 and Swift 5

Sign up for our iOS development course Zero to App Store and learn how to build professional iOS 12 apps with Swift 5 and Xcode 10.

How Closure Types Work

Every closure has a type, just like variables.

There’s no conceptual difference between a variable that’s declared as type Int and a constant declared as type (Int) -> (). The types are different, but they both are types.

Let’s decompose closure types to better understand how closures work. In it’s most essential form, this is a closure expression:

let closureName:(ParameterTypes) -> ReturnType = { (parameterName:ParameterType) in


A closure with no parameters and no return type, has the following closure type:

() -> ()

The above expression consists of two empty tuples () and a single arrow ->. The first tuple is the input for the closure, and the second tuple is the output for the closure.

A tuple is an ordered list of items, like (a, b, c). It’s comma-separated and wrapped in parentheses. An example in Swift: let flight = (airport: "LAX", airplane: 747).

You can also write Void for the closure’s return type, like this:

() -> Void

In Swift, Void means “nothing”. When your function returns Void, it does not return anything. Not even nil or an empty string! As described in the Apple Developer Documentation, Void is an alias of an empty tuple (). When a function or closure doesn’t return anything, it’s return type is Void.

You declare a closure with one parameter like this:

let birthday:(String) -> Void = { (name:String) -> Void in

That looks complicated, right? Here’s how it works:

  • The stuff between the squiggly brackets { and } is the closure expression. The closure assigned to the constant birthday with the let keyword.
  • The first (String) -> Void is the closure type. This closure has one parameter of type String and it returns Void, which means “nothing”.
  • The second (name:String) -> Void is the same closure type, except that it names the first parameter of the closure as name. It’s part of the closure expression.
  • The in keyword separates the closure parameters and the closure body. Before in comes the closure type, and after in you write the code of the closure.

Let’s look at one last example. The following function has two parameters, and returns a value:

let greeting:(String, String) -> String = { (time:String, name:String) -> String in
return "Good \(time), \(name)!"

let text = greeting("morning", "Arthur")

The closure greeting has two parameters, both of type String. The closure returns a value of type String. The type of greeting is defined explicitly, and so are the closure parameters in the closure expression itself.

When the closure is called, it is provided two arguments of type String, and its return value is assigned to text, and then printed out.

See how there are two parameters of type String, and a return type of type String? The closure type (String, String) -> String defines the input and output for the closure, and the expression (time:String, name:String) gives the input parameters local variable names.

As you’ll find out in the next chapter, you can write the exact same closure expression also like this:

let greeting:(String, String) -> String = { "Good \($0), \($1)!" }

let text = greeting("morning", "Arthur")

Let’s summarize:

  • Every closure has a type, that you define in the closure expression
  • The most basic closure type is () -> Void, with no input parameters and no return value
  • You can explicitly define a closure type when declaring a variable or constant, such as let greeting:(String) -> Void ...
  • The closure expression repeats the closure type and gives every closure parameters a name, like ... { (name:String) -> Void in ...

Awesome! Let’s move on.

Closures And Type Inference

Swift has a super useful feature called type inference. When you don’t explicitly specify the type of a variable, Swift can figure out on its own what the type of that variable is. It does so based on the context of your code.

Here’s an example:

let age = 104

What’s the type of age? Swift will infer the type of age based on the context of the above code. That 104 is a literal value for an integer number, so the constant age has the type Int.

Important: Swift is a strong-typed programming language. That means that every value has a type, even if it is inferred! Never mistake type inference for “this value has no type”. A value always has a type, you just don’t declare it explicitly. Type inference can lead to confusion. Make sure you always know the types of your variables!

Type inference and closures go hand-in-hand. As a result, you can leave out many parts of a closure expression. Swift will (usually) infer those parts on its own.

Let’s look at an example:

let names = ["Zaphod", "Slartibartfast", "Trillian", "Ford", "Arthur", "Marvin"]
let sortedNames = names.sorted(by: <)

In the above code you’re creating an array with names, then sorting it alphabetically, then assigning the result to sortedNames, and then printing out the array of sorted names.

The key part of this example is names.sorted(by: <). That first parameter by: takes a closure.

Now… get a load of this. First, when we expand < it becomes this:

names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 < s2

Then, that’s the same as…

names.sorted(by: { s1, s2 in return s1 < s2 } )

And it’s also exactly the same as…

names.sorted(by: { s1, s2 in s1 < s2 } )

And you can also write that like…

names.sorted(by: { $0 < $1 } )

With trailing closure syntax, you can even write it as:

names.sorted { $0 < $1 }

And finally, you can also just use the < operator as a function:

names.sorted(by: <)

Neat, right? All thanks to type inference and closure expressions. Here’s the gist of every closure expression, starting with the most expanded one:

  1. The first example uses the complete closure syntax, including two parameter names s1 and s2 of type String, and a return type of Bool. It also uses the in and return keywords.
  2. The second example omits the closure parameters, because they can be inferred from context. Since we’re sorting an array of strings, those two parameters are inferred as String.
  3. The third example uses the $0 and $1 shorthand to reference the first and second parameters of the closure. The parameters are there, they just don’t have names! Again, with inferred String types.
  4. The fourth example uses trailing closure syntax. When a closure is the last (or only) parameter of a function, you can write the closure outside the function’s parentheses.
  5. And the last example uses the smaller-than operator < as the closure. In Swift, operators are top-level functions. Its type is (lhs: (), rhs: ()) -> Bool, and that fits the type of sorted(by:) perfectly!

It’s hopefully now that you’re starting to see the power of closures, their expressivity, and how the Swift programming language enables you to write elegant code.

It helps to practice Swift coding, and working with closures, as often as you can. Practice makes perfect! Don’t wait to learn about closures until you’ve ran into trouble. Set aside some of your time to discover and grasp new programming concepts and best practices.

Personally, I like the sorted { $0 < $1 } expression most. Remember that it’s not always smartest to choose the most concise programming approach. In fact, it helps to be more descriptive than clever!

By the way, closures can get crazy really quickly. Check this out:

let squared = { $0 * $0 }(12)

The closure { $0 * $0 } is immediately called with (), right after defining it, effectively assigning the result of 12 * 12 to squared. Executing a closure directly after it is defined is the basis for lazy computed properties.

So, to summarize:

  • Swift uses type inference to figure out the type of a variable or constant when it isn’t explicitly provided
  • Closures and type inference go well together, so you can leave out parts of a closure expression to improve readability – but not too much
  • Closure expressions can get crazy quickly, so it helps to practice using closures and play around with them

Learn how to code your own iOS apps by mastering Swift 5 and Xcode 10 » Find out how

Capturing And The Capture List

Seeing a closure as a function you can assign to a variable doesn’t do the concept of closures the justice it deserves. When I explained closures like that, I left out an important part: capturing.

The name “closure” comes from “enclosing”, and when we stretch it, it comes from the functional programming concept of “closing over”. In Swift, a closure captures variables and constants from its surrounding scope.

The words “closing over”, “capturing” and “enclosing” all mean the same thing here.

Every variable, function and closure in Swift has a scope. Scope determines where you can access a particular variable, function or closure. If a variable, function or closure isn’t in a scope, you can’t access it. Scope is sometimes called “context”.

Compare it to the “context” you define your house keys in. When you’re outside the house, and your keys are inside the house, they’re not in the same context, and you can’t unlock the front door. When both you and the house keys are outside the house, you’re in the same context, and you can get in.

Your code has global scopes and local scopes. Some examples:

  • A property, as defined within a class, is part of the global class scope. Anywhere within that class you can set and get the value of the property.
  • A variable, as defined in a function, is part of the local function scope. Anywhere within that function you can set and get the value of the property.

Let’s look at an example of how a closure captures its surrounding scope.

let name = "Zaphod"

let greeting = {
print("Don't panic, \(name)!")


Here’s what happens in the code:

  • First, a constant name is defined with value "Zaphod" of type String.
  • Then, a closure is defined and assigned to greeting. The closure prints out some text.
  • Finally, the closure is executed by calling greeting().

In the example, the closure closes over the local variable name. It encapsulates variables that are available in the scope that the closure is defined in. As a result, we can access name even though it’s not declared locally within the closure!

A smart reader will now point out that name is accessible in the closure’s body because in a REPL environment, such as a sandbox or Swift Playground, top-level variables are part of the global scope. Yes, that’s right! You can access them anywhere. Please check the next example.

Let’s look at a more complex example:

func addScore(_ points: Int) -> Int
let score = 42

let calculate = {
return score + points

return calculate()

let value = addScore(11)

What happens here?

  • First, a function addScore(_:) is defined. It will return a score based on the parameter points and the “previous” score of 42.
  • Then, within the function a closure calculate is defined. It simply adds score and points and returns the result. The function calls the closure with calculate().
  • Finally, the function addScore(_:) is called, assigned to value, and printed out.

The closure calculate captures both score and points! Neither of those variables are declared locally within the closure, yet the closure can get their values. That’s because of capturing.

This color diagram explains it better visually:

Closure Swift Scope explained visually

See how there are different scopes? And see how the closure scope has access to score and points, that are part of the local function scope?

There are a few things worth noting here:

  1. A closure only captures variables etc. that are actually used in the closure. When a variable isn’t accessed in the closure, it isn’t captured.
  2. A closure can capture variables and constants. You can technically also capture other closures, because they’re variables or constants too, and properties, because a property belongs an object that’s assigned to a variable or constant. And you can of course call functions, classes etc. in a closure, but they aren’t captured.
  3. Capturing only works one way. The closure captures the scope it is defined in, but code “outside” a closure doesn’t have access to values “inside” the closure.

Capturing values with closures can lead to all sorts of fun with strong reference cycles and memory leaks. And that’s where the capture list comes in.

First things first. When a closure captures a value, it automatically creates a strong reference to that value. When Bob has a strong reference to Alice, then Alice isn’t removed from memory until Bob is removed from memory.

That usually goes OK. But what if Alice has a strong reference back to Bob? Then both Bob and Alice won’t be removed from memory, because they’re holding on to each other. Bob can’t be removed because Alice is holding onto him, and Alice can’t be removed because Bob is holding onto her.

This is called a strong reference cycle and it causes a memory leak. Imagine a hundred Bob’s and Alice’s taking up 10 MB each in memory, and you see what the problem is: less memory for other apps, and no way to remove it.

Memory in iOS is managed with a concept called Automatic Reference Counting. It’s different than garbage collection. Most of memory management with ARC is done for you, but you have to avoid strong reference cycles. Memory management is a hairy subject, so we’ll leave that for another article.

You can break a strong reference cycle with a capture list. Just like you can mark a class property as weak, you can mark captured values in a closure as a weak or unowned reference. Their default is strong.

Here’s an example:

class Database {
var data = 0

let database = Database() = 11010101

let calculate = { [weak database] multiplier in
return database!.data * multiplier

let result = calculate(2)

Here’s what happens in the code:

  • First, you define a class Database. It has one property data. It’s a fictional class, so imagine that it’s super memory intensive…
  • Then, you create an instance of Database, assign it to database, and set its data property to some integer value.
  • Then, you define a closure calculate. The closure takes one argument multiplier, and it captures database. Within the closure, the data is simply multiplied by multiplier.
  • Finally, the closure is called with argument 2 and its result is assigned to result.

The key part is the capture list, here:

... { [weak database] ...

A capture list is a comma-separated list of variable names, prepended with weak or unowned, and wrapped in square brackets. Some examples:

[weak self, unowned tableView]
[weak navigationController]
[unowned self, weak database]

You use a capture list to specify that a particular captured value needs to be referenced as weak or unowned. Both weak and unowned break the strong reference cycle, so the closure won’t hold on to the captured object.

Here’s what they mean:

  • The weak keyword indicates that the captured value can become nil
  • The unowned keyword indicates that the captured value never becomes nil

Both weak and unowned are the opposite of a strong reference, with the difference that weak indicates a variable that can become nil at some point.

You typically use unowned when the closure and the captured value will always refer to each other, and will always be deallocated at the same time. An example is [unowned self] in a view controller, where the closure will never outlive the view controller.

You typically use weak when the captured value at some point becomes nil. This can happen when the closure outlives the context it was created in, such as a view controller that’s deallocated before a lengthy task is completed. As a result, the captured value is an optional.

The concept of capturing, capture lists and memory management is tricky. It’s not that complicated, but I think it’s just hard to visualize such an abstract concept. In practical iOS development, the most common capture list is [weak self] or [unowned self].

As you’re reading this, just take note of the concepts and refer back to them whenever you get into trouble with memory management. Xcode will tell you when you’re using self in a closure, and if you do, that might be a good time to read up on the exact inner workings of capture lists.

Here’s what you learned so far:

  • A closure can capture its surrounding scope, making variables and constants from that scope accessible within the closure
  • Variables and constants are captured with a strong reference by default, which can cause a strong reference cycle
  • You can break the strong reference cycle with a capture list, by explicitly marking captured values as weak and unowned

Let’s move on to the next and last section about completion handlers!

Closures In Action: The Completion Handler

So what do you actually use closures for? They are incredibly powerful tools, but if you can’t put them to use, they won’t do you much good.

A common application of a closure is the completion handler. It works roughly like this:

  • You’re executing a lengthy task in your code, like downloading a file, making a calculation, or waiting for a webservice request
  • You want to execute some code when the lengthy task is completed, but you don’t want to “poll” the task continuously to check if it’s finished
  • Instead, you provide a closure to the lengthy task, which it will call when the task is completed

Here’s an example:

Webservice.request("", completionHandler: { data in
    // Do something with "data"

In the above code we’re making a hypothetical networking request to download some data and do something with that data when the request is completed.

That may not seem shocking, so here’s the kicker:

  • We’re starting the networking request at point A
  • The completion handler is executed at point B
  • The completion handler can capture the scope at point A

It’s like time travel! You can code as if there’s no passing of time between starting the request and its completion. Instead of waiting for the lengthy task to complete, we can just provide a bit of code that’s executed when the task completes, while we’re coding the networking request.

When you pass a closure as an argument for a function, and when that closure outlives the function it was passed to, it is said to be an escaping closure. Since Swift 3, closures are non-escaping by default. When you declare a function that takes a closure, and when that closure is escaping, its parameter has to be marked with @escaping. Read more about @escaping here.

Check out this diagram:

Closure Swift Timeline of completion handler

Here’s what happens:

  • A lengthy task starts, and we’re defining a completion handler. The completion handler is a closure, so it captures variables in its surrounding scope.
  • The lengthy task completes, and your completion handler is executed. The closure scope is kept around, so we can use any variables and constants defined in the closure’s scope.

The key part is this: we can code the completion handler at the same time we’re starting the lengthy task!

Imagine you want to use the data of a networking request to display an image:

let imageView = UIImageView()

Webservice.request("", completionHandler: { data in
    imageView.image = data

See what happens here? You’re defining an image view, starting the networking request, and providing a completion handler. The completion handler is executed when the lengthy task is completed.

The closure has captured a reference to imageView, so you can set the image data when the lengthy task is completed. This happens in the future, but we’re still in the present! You can code it as if the lengthy task executes instantly.

Let’s compare that to target-action, another common approach to invoke functions at a later point in time.

func download()
    let imageView = UIImageView()

    Webservice.request("", target: self, action: #selector(onDownloadComplete(_:)))    

func onDownloadComplete(_ data: UIImage)
    // Oh no! How do I get to the `imageView` from here?

See how that’s different?

Even though you can use target-action to determine what happens when a lengthy task completes, you don’t have the benefits of capturing, so you can’t respond to the completion in the here-and-now.

The smart reader now points out that you can make imageView an instance property, so you can access it in onDownloadComplete(_:). Yes, that’s a good alternative! But what if you need access to multiple variables? You don’t want to add unnecessary clutter.

OK, before we call it quits, let’s look at one last bit of magic with closures. Consider the following code:

func lengthyTask(completionHandler: (Int) -> Int)
let result = completionHandler(42)

lengthyTask(completionHandler: { number in
return 101

Try it! What’s happening there?

  • First, we define a function lengthyTask(completionHandler:). When that function is executed, the completion handler is executed with one argument 42. The completion handler also returns a result, which is printed out.
  • Then, the function lengthyTask(completionHandler:) is called, and provided with a closure. The closure has one argument, as defined earlier, which is printed out with print(number), and it also returns a value 101.

As a result, the first print() will print out 101, and the second print() will print out 42. Wait a minute…

Is the closure providing a value back to the function that calls the closure? YES! It’s magical…

Because the closure is executed in lengthyTask, but defined earlier, you can return values from your closure just like any other function. The return value ends up with whomever called the closure!

Learn how to build iOS apps

Get started with iOS 12 and Swift 5

Sign up for our iOS development course Zero to App Store and learn how to build professional iOS 12 apps with Swift 5 and Xcode 10.

Further Reading

And that, dear developer, is how closures work. Pfew! Quite fascinating and thrilling, right? It’s mind-boggling!

Here’s what you learned:

  • How closures work and how you can use them
  • The syntax for declaring and calling closures
  • How to compose and decompose a closure’s type
  • What “closing over” means and how to use a capture list
  • The single best use for closures: the completion handler
  • … and a whole bunch of stuff in-between

One last thing… if closure syntax ever has you beat, check out for the skinny.

Want to learn more? Check out these resources:

Enjoyed this article? Please share it!

Reinder de Vries

Reinder de Vries

Reinder de Vries is a professional iOS developer. He teaches app developers how to build their own apps at Since 2009 he has developed a few dozen apps for iOS, worked for global brands and lead development at several startups. When he’s not coding, he enjoys strong espresso and traveling.