LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 618 min read

Functions & Closures

Learn function parameters and labels, default and variadic parameters, inout, multiple return values, and closures with capturing.

Introduction

Functions are the basic unit of reusable logic in Swift, and closures — self-contained blocks of code that can be passed around like values — are just as central. Swift treats functions as a special case of closures, which is why they share so much syntax.

This lesson covers declaring functions, Swift's distinctive external/internal parameter label system, default and variadic parameters, `inout` parameters, multiple return values via tuples, and how to write and use closures.

Function Basics

A function is declared with `func`, a name, a parameter list in parentheses, and an optional return type after `->`.

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

Click Run to see what this code prints.

Parameter Labels

Swift functions are called using argument labels by default, making call sites read like natural language. Each parameter can have a separate external label (used at the call site) and internal name (used inside the function body); using an underscore `_` as the external label omits it entirely.

func greet(to name: String, from sender: String) -> String {
return "Hi \(name), regards from \(sender)"
}
print(greet(to: "Meera", from: "Dev Team"))
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
print(add(3, 4))
Output

Click Run to see what this code prints.

Default & Variadic Parameters

Parameters can have default values, making them optional at the call site, and a function can accept a variadic parameter that collects any number of values into an array.

func power(base: Int, exponent: Int = 2) -> Int {
var result = 1
for _ in 0..<exponent { result *= base }
return result
}
print(power(base: 3)) // uses default exponent: 9
print(power(base: 2, exponent: 5)) // 32
func total(_ numbers: Int...) -> Int {
numbers.reduce(0, +)
}
print(total(1, 2, 3, 4)) // 10

inout Parameters

By default, Swift function parameters are constants — you can't modify them inside the function. Marking a parameter `inout` lets the function modify the caller's original variable directly; you must pass it with an `&` prefix at the call site.

func doubleInPlace(_ value: inout Int) {
value *= 2
}
var number = 10
doubleInPlace(&number)
print(number)
Output

Click Run to see what this code prints.

Multiple Return Values

Since Swift has tuples, a function can return several values at once without defining a dedicated type for them.

func minMax(of numbers: [Int]) -> (min: Int, max: Int) {
(numbers.min()!, numbers.max()!)
}
let result = minMax(of: [5, 3, 9, 1])
print("min: \(result.min), max: \(result.max)")
Output

Click Run to see what this code prints.

Closures Basics

A closure is a self-contained block of functionality that can be stored in a variable, passed as an argument, and called later — very similar to a function, but written inline without a name.

let square: (Int) -> Int = { number in
return number * number
}
print(square(5))
Output

Click Run to see what this code prints.

Shorthand Closure Syntax

Swift lets you drop the parameter names entirely and refer to arguments positionally as `$0`, `$1`, and so on, which is idiomatic for short closures passed to functions like `map`.

let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { $0 * 2 }
print(doubled)
Output

Click Run to see what this code prints.

Trailing Closures

When a closure is a function's last argument, Swift lets you write it outside the parentheses, called trailing closure syntax — one of the most common patterns you'll see in real Swift code.

func performOperation(on value: Int, operation: (Int) -> Int) -> Int {
operation(value)
}
let result2 = performOperation(on: 6) { $0 * $0 }
print(result2)
Output

Click Run to see what this code prints.

Capturing Values

Closures capture (hold onto) variables and constants from the surrounding scope where they're defined, even after that scope has finished executing — this is what makes closures so powerful for things like counters or callbacks.

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

Click Run to see what this code prints.

Common Mistakes

Avoid These Mistakes
  • Forgetting that argument labels are required by default at call sites unless you explicitly use `_` for the external name.
  • Assuming `inout` gives class-like reference semantics for every type — it only lets the function reassign the caller's variable, it does not change the value type's copy semantics.
  • Capturing `self` strongly inside a long-lived closure (e.g. stored as a property), which can create a retain cycle and leak memory — covered further in Best Practices.
  • Overusing `$0`/`$1` shorthand in closures complex enough that named parameters would read more clearly.

Best Practices

  • Use trailing closure syntax when a closure is a function's final argument — it's the idiomatic, widely expected style.
  • Give closures named parameters instead of `$0`/`$1` once the logic inside grows beyond a single simple expression.
  • Prefer returning a tuple over an `inout` parameter when a function just needs to produce multiple values, reserving `inout` for genuine in-place mutation.
  • Keep functions focused on one task — a function that needs many parameters with several defaults is often a signal to split it up.

Frequently Asked Questions

Functions are technically a special, named case of closures. Both can capture their surrounding context and be passed around as values, but functions always have a name and closures are typically defined inline.

Use variadic parameters (like `Int...`) when callers naturally pass a handful of individual values at the call site; use an array parameter when the caller already has a collection built up.

The `&` is a visual signal at the call site that the function might modify this variable — it makes potential mutation explicit rather than hidden.

Key Takeaways

  • Functions use external/internal parameter labels, with `_` to omit a label at the call site.
  • Parameters can have default values and one variadic parameter is allowed per function.
  • `inout` parameters, marked with `&` at the call site, let a function modify the caller's variable.
  • Tuples enable returning multiple values from a single function.
  • Closures capture surrounding variables and support trailing and shorthand ($0) syntax.

Summary

You can now write functions with expressive, labeled parameters and use closures — including trailing closure syntax — to pass behavior around as values. Next, you'll tackle Swift's signature feature: Optionals.

Next Lesson →

Optionals