Swift Closures Tutorial

Learn how closures let you store, pass, and run blocks of Swift code without giving every block a separate function name.
Written by

Iñaki Narciso

Updated on

Apr 06 2026

Table of contents

    Overview

    A closure is a self-contained block of code that can be stored in a variable, passed into a function, or returned from a function. Closures are similar to functions, but they can be written inline when you only need a small bit of behavior.

    You will see closures all over Swift, especially when sorting arrays, handling button taps, responding to network requests, and working with SwiftUI.

    Function vs Closure

    A function has a name and is declared with the func keyword. A closure can be assigned to a constant or variable instead:

    func sayHello() {
        print("Hello!")
    }
    
    let sayHi = {
        print("Hi!")
    }
    
    sayHello()
    sayHi()

    The sayHi constant stores a closure. Calling sayHi() runs the code inside that closure.

    Closure Syntax

    When a closure needs input or returns a value, write its parameters, return type, and body inside braces. The in keyword separates the closure signature from the code it runs.

    let greet = { (name: String) -> String in
        return "Hello, \(name)!"
    }
    
    let message = greet("Chris")
    print(message)

    You can read the first line as: this closure takes a String named name and returns a String.

    Closures as Function Parameters

    Closures become especially useful when a function needs to run custom code that the caller provides.

    func runTask(_ task: () -> Void) {
        print("Starting")
        task()
        print("Finished")
    }
    
    runTask {
        print("Doing the work")
    }

    The type () -> Void means the closure takes no input and returns no value. Because the closure is the final argument, Swift lets you write it after the function call parentheses. This is called trailing closure syntax.

    Sorting with Closures

    A common beginner-friendly example is sorting. The sorted method can receive a closure that decides which of two values should come first.

    let names = ["Maya", "Chris", "Alex"]
    
    let sortedNames = names.sorted { first, second in
        first < second
    }
    
    print(sortedNames)

    The closure receives two names and returns true when the first name should appear before the second name.

    Shorthand Argument Names

    Swift can infer a lot from context. In short closures, you can use shorthand argument names like $0 and $1 instead of naming each parameter yourself.

    let numbers = [3, 1, 5, 2]
    
    let sortedNumbers = numbers.sorted {
        $0 < $1
    }
    
    print(sortedNumbers)

    Here, $0 is the first number being compared and $1 is the second. Shorthand is convenient, but named parameters are often clearer when the closure does more than one simple expression.

    Capturing Values

    Closures can capture values from the surrounding scope. That means the closure can keep access to a variable even after the surrounding function has finished.

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

    The returned closure keeps access to count. Each time you call counter(), the same captured value is updated.

    Escaping Closures

    Most closures passed into a function run before that function returns. An escaping closure can be saved and run later, after the original function has already finished.

    var savedCompletion: (() -> Void)?
    
    func saveForLater(_ completion: @escaping () -> Void) {
        savedCompletion = completion
    }
    
    saveForLater {
        print("Run later")
    }
    
    savedCompletion?()

    The @escaping keyword tells Swift that completion may outlive the function call. You will often see this pattern with asynchronous work.

    Common Mistakes

    Confusing closure syntax with function syntax

    Closures do not use the func keyword. Their parameters and return type go inside the braces before in.

    Overusing shorthand names

    Names like $0 and $1 are great for tiny comparisons. If the closure grows, named parameters usually make the code easier to read.

    Capturing self strongly in long-lived closures

    When a closure is stored or runs later, be careful about capturing objects such as view models or view controllers. In app code, this is where you may need [weak self] to avoid keeping something alive longer than intended.

    Quick Reference

    SyntaxWhat It Means
    { print("Hi") }A closure with no parameters and no return value
    { (name: String) -> String in ... }A closure that takes a string and returns a string
    () -> VoidA closure type with no input and no return value
    (Int) -> StringA closure type that takes an integer and returns a string
    $0, $1Shorthand names for the first and second closure arguments
    @escapingMarks a closure that can be stored or run after the function returns

    Summary

    • Closures are blocks of code you can store, pass around, and run later.
    • Closures are similar to functions, but they can be written inline without a separate name.
    • The in keyword separates a closure's signature from its body.
    • Trailing closure syntax keeps function calls readable when the final argument is a closure.
    • Shorthand arguments like $0 and $1 are useful for short closures.
    • Closures can capture values from their surrounding scope.
    • Use @escaping when a closure may run after the function returns.

    Closures can look unusual at first, but they are one of Swift's most practical tools. Once the syntax clicks, you will start recognizing closures in many everyday Swift and SwiftUI APIs.