Swift
Closure
Variable
Programming
iOS Development

Store a closure as a variable in Swift

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

In Swift, a closure is just a value of function type, which means you can store it in a variable, pass it around, and return it from another function. The important part is declaring the closure's parameter and return types clearly enough that the compiler and future readers know what the variable is supposed to do.

Store a simple closure in a variable

A closure variable is declared with a function type such as (Int) -> Int or (String) -> Void. Then you assign a closure expression to it.

swift
1import Foundation
2
3let doubleValue: (Int) -> Int = { number in
4    return number * 2
5}
6
7print(doubleValue(21))

The variable doubleValue stores a closure that accepts one Int and returns one Int. Because the type is explicit, the closure body can stay compact.

Use var when the closure needs to change

If the closure is a configurable callback, declare it with var so a different closure can be assigned later.

swift
1import Foundation
2
3var formatter: (String) -> String = { text in
4    text.uppercased()
5}
6
7print(formatter("swift"))
8
9formatter = { text in
10    "Hello, \(text)!"
11}
12
13print(formatter("Swift"))

This pattern is common for strategy-style behavior, test doubles, and UI callbacks where the implementation changes based on state.

Closures can capture surrounding values

A stored closure can capture constants and variables from the scope where it was created. That is one of the reasons closures are so useful, but it is also why they need careful ownership handling in reference types.

swift
1import Foundation
2
3func makeAdder(base: Int) -> (Int) -> Int {
4    let add: (Int) -> Int = { value in
5        value + base
6    }
7    return add
8}
9
10let addFive = makeAdder(base: 5)
11print(addFive(10))

The returned closure still knows base even after makeAdder has finished. That captured state is part of the closure value.

Storing closures as properties

Closures are often stored as properties on structs and classes. This is common in view models, completion handlers, and dependency injection.

swift
1import Foundation
2
3final class Downloader {
4    var onComplete: ((String) -> Void)?
5
6    func simulateDownload() {
7        let result = "finished"
8        onComplete?(result)
9    }
10}
11
12let downloader = Downloader()
13downloader.onComplete = { message in
14    print("Download \(message)")
15}
16downloader.simulateDownload()

The double parentheses in ((String) -> Void)? mean the property is an optional closure. That is a common and useful pattern when the callback may or may not be assigned.

Be careful about retain cycles in classes

When a class stores a closure and that closure captures the same instance strongly, you can create a retain cycle. That is one of the most important practical issues with stored closures in Swift.

swift
1import Foundation
2
3final class Greeter {
4    var message = "Hello"
5    lazy var printer: () -> Void = { [weak self] in
6        print(self?.message ?? "missing")
7    }
8}
9
10let greeter = Greeter()
11greeter.printer()

The capture list [weak self] prevents the closure from keeping the object alive forever. Not every stored closure needs this, but class-owned callbacks often do.

Type aliases make closure variables easier to read

If the signature is long, use a type alias. That makes stored closures more maintainable and reduces repetition.

swift
1import Foundation
2
3typealias CompletionHandler = (Result<String, Error>) -> Void
4
5let handler: CompletionHandler = { result in
6    print(result)
7}
8
9handler(.success("ok"))

This is especially helpful in APIs with several related callback types.

Common Pitfalls

  • Forgetting to declare the closure type clearly, which makes the code harder to read.
  • Using let when the closure needs to be replaced later.
  • Storing a closure on a class and capturing self strongly, creating a retain cycle.
  • Overusing optional closures when a required dependency would be clearer.
  • Writing overly complex closure bodies when a named method would communicate intent better.

Summary

  • In Swift, closures are values and can be stored in variables or properties.
  • Declare the closure type explicitly for clarity.
  • Use var when the closure should be replaceable.
  • Stored closures can capture surrounding values and state.
  • In classes, watch for retain cycles and use capture lists when needed.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.