Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Swift  Module 4 – Functions and closures

Functions, argument labels and parameters

Declare Swift functions whose calls read as phrases: argument labels, default values, variadic and inout parameters, tuple returns and overloads.

  • Beginner
  • 20 minutes
  • Examples run with Swift 6.4 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Declare functions with argument labels, parameter names, default values and variadic parameters
  • Write functions that return a value, a tuple, an optional tuple or nothing, using implicit returns
  • Change a caller's variable with an inout parameter, and explain why one variable cannot fill two of them
  • Predict which overloaded function a call runs, from its labels and the types of its arguments
  • Name functions and labels so that each call reads as a phrase, following the Swift API Design Guidelines

Before you start

What you’ll learn · Before you start

What you will learn

  • Declare functions with argument labels, parameter names, default values and variadic parameters
  • Write functions that return a value, a tuple, an optional tuple or nothing, using implicit returns
  • Change a caller's variable with an inout parameter, and explain why one variable cannot fill two of them
  • Predict which overloaded function a call runs, from its labels and the types of its arguments
  • Name functions and labels so that each call reads as a phrase, following the Swift API Design Guidelines

Before you start

On this page

A call such as fare(3, 11, true) hides its meaning: is 3 where the journey starts or how many people travel, and what does true switch on? With Swift’s argument labels the same call reads fare(fromKm: 3, toKm: 11, class: .first), and in this lesson’s made-up fare table it costs 40 rupees, twice the standard fare of 20.

You will declare functions whose calls read like that, let callers leave out arguments that have defaults, take any number of fares with a variadic parameter, return a tuple or nothing at all, change a caller’s variable with inout, see why one variable cannot fill two inout parameters, and name functions the way the Swift API Design Guidelines advise.

A parameter has a label for the call and a name for the body

In labels.swift the fare function is declared as fare(fromKm start: Int, toKm end: Int, class travelClass: TravelClass). Each parameter has two names. The first is the argument label: The name a call writes before an argument, as fromKm in fare(fromKm: 3, toKm: 11). It may differ from the parameter name the function's body uses, and an underscore in its place means the call writes no label., which the caller writes: fromKm: 3. The second is the parameter name, which only the body uses: start. The Swift Programming Language gives every function parameter both. Step through the call and watch each value take its parameter’s name:

Interactive · step by step How fare(fromKm:toKm:class:) receives its arguments Step through the call from labels.swift. The left column is what the call writes; the right column is the name each value has in the body.
How fare(fromKm:toKm:class:) receives its arguments, step 5 of 5. Showing fromKm: 3, toKm: 11, class: .first, start = 3, end = 11, travelClass = .first, returns 40 (8 km: 20, first class: 40). In focus: returns 40.fromKm: 3argument labeltoKm: 11argument labelclass: .firsta keyword as a labelstart = 3parameter nameend = 11parameter nametravelClass = .firstparameter namereturns 408 km: 20, first class: 40

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. The call. fare(fromKm: 3, toKm: 11, class: .first) writes each argument after its label, in the order the declaration lists the parameters.
  2. fromKm becomes start. The declaration says fromKm start: Int. fromKm is the label the caller writes and start is the name the body uses, so 3 arrives as start.
  3. toKm becomes end. toKm end: Int works the same way: the caller writes toKm: 11, and inside the function the value is called end.
  4. A keyword as a label. class is a keyword, and a keyword other than inout, var or let may still be a label. The body could only use it as a name in backticks, so the parameter gets its own name, travelClass.
  5. The body uses its names. The body reads start, end and travelClass, never the labels: abs(end - start) is 8 km, a base fare of 20 in this lesson's table, doubled for first class. The call returns 40.

Three patterns cover nearly every declaration. Two names, as in fromKm start, give the caller a phrase and the body a short name. One name, as in farePerKm(fare: Int, km: Int), serves as both, because a parameter’s name is its label unless you write another. An underscore, as in isPeakHour(_ hour: Int), removes the label, so the call is isPeakHour(9): the function’s name already says what 9 is. A keyword other than inout, var and let may be a label without backticks, which is how class: can label a parameter whose name in the body is travelClass.

labels.swift: two names, one name and no label Swift · labels.swift
// labels.swift: an argument label for the call, a parameter name for the body.
enum TravelClass {
    case standard, first
}

// The fare table of this lesson, made up for it: rupees by distance.
func fare(fromKm start: Int, toKm end: Int, class travelClass: TravelClass) -> Int {
    let base = switch abs(end - start) {
    case 0...3: 10
    case 4...8: 20
    case 9...15: 30
    case 16...25: 40
    default: 50
    }
    return travelClass == .first ? base * 2 : base
}

// One name: it is the argument label and the parameter name.
func farePerKm(fare: Int, km: Int) -> Double {
    Double(fare) / Double(km)
}

// An underscore: the call has no label, because the function's name says enough.
func isPeakHour(_ hour: Int) -> Bool {
    (8...10).contains(hour) || (17...20).contains(hour)
}

print("standard, km 3 to 11:", fare(fromKm: 3, toKm: 11, class: .standard))
print("first, km 3 to 11:", fare(fromKm: 3, toKm: 11, class: .first))
print("per km:", farePerKm(fare: 20, km: 8))
print("peak at 9:", isPeakHour(9), "peak at 14:", isPeakHour(14))

Output

standard, km 3 to 11: 20
first, km 3 to 11: 40
per km: 2.5
peak at 9: true peak at 14: false

Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O labels.swift -o labels && ./labels

A call writes the labels exactly as declared, in the declaration’s order. Here is what the compiler says to one call that uses the parameter names instead, and to one that leaves the labels out:

wrong_labels.swift: names and bare values where labels belong Swift · wrong_labels.swift
// wrong_labels.swift: calls that use the parameter names, or no labels, where labels belong.
func fare(fromKm start: Int, toKm end: Int) -> Int {
    abs(end - start) <= 3 ? 10 : 20
}

print(fare(start: 3, end: 11))
print(fare(3, 11))
Compiling wrong_labels.swift Python · labels_session.py
from session import run, version

# wrong_labels.swift calls fare(fromKm:toKm:) once with its parameter names and once with no labels at all.
version()
run("""
!swiftc wrong_labels.swift -o wrong_labels
""")

Output

# Apple Swift version 6.4 (swiftlang-6.4.0.34.1)
$ swiftc wrong_labels.swift -o wrong_labels
wrong_labels.swift:6:11: error: incorrect argument labels in call (have 'start:end:', expected 'fromKm:toKm:')
4 | }
5 | 
6 | print(fare(start: 3, end: 11))
  |           `- error: incorrect argument labels in call (have 'start:end:', expected 'fromKm:toKm:')
7 | print(fare(3, 11))
8 | 

wrong_labels.swift:7:11: error: missing argument labels 'fromKm:toKm:' in call
5 | 
6 | print(fare(start: 3, end: 11))
7 | print(fare(3, 11))
  |           `- error: missing argument labels 'fromKm:toKm:' in call
8 | 
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 labels_session.py

Each diagnostic: A message from the compiler about the code: an error, which stops the build, a warning, which does not, or a note that adds detail. Each starts with the file, the line and the column. More in “Compiler errors, language modes and feature flags” names the labels the call should have written, fromKm:toKm:. The labels belong to the call and the names to the body, and neither can stand in for the other.

Writing the parameter names in the call

fare(start: 3, end: 11) and fare(3, 11) do not compile, because fromKm: and toKm: are part of every call. Write the labels the declaration gives, or declare the parameter with _ when a bare value reads well, as isPeakHour(9) does.

Checkpoint

Check: Argument labels and parameter names

  1. Question 1 of 2 Given func pay(_ amount: Int, from card: String), which call compiles?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: pay(40, from: "Asha")

    A call writes the argument labels, never the parameter names. Here the first label is removed with _ and the second is from, so the call is pay(40, from: "Asha").

  2. Question 2 of 2 What does the underscore do in func isPeakHour(_ hour: Int)?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It removes the argument label, so the call is isPeakHour(9)

    An underscore before the parameter name suppresses the argument label. The parameter name, hour, is still what the body uses.

Default values and variadic parameters let one function take many calls

Most journeys are standard class, so asking every caller to say so is noise. A default value: A value written after a parameter's type, as in class travelClass: TravelClass = .standard. A call may leave that argument out, and the function then uses the default., written after the parameter’s type, lets a call leave that argument out: with class travelClass: TravelClass = .standard, the call fare(fromKm: 3, toKm: 11) is a standard fare. A variadic parameter: A parameter written with three dots after its type, as in fares: Int..., that takes zero or more separate values. The function receives them as an array, here [Int]; an array cannot be passed in their place. goes the other way and takes any number of values, written with three dots after its type: fares: Int.... This program uses both, and prints what the variadic parameter holds inside the function:

defaults_variadic.swift: a default value, one variadic parameter, then two Swift · defaults_variadic.swift
// defaults_variadic.swift: a default value, a variadic parameter, and two variadic parameters.
enum TravelClass {
    case standard, first
}

func fare(fromKm start: Int, toKm end: Int, class travelClass: TravelClass = .standard) -> Int {
    let base = switch abs(end - start) {
    case 0...3: 10
    case 4...8: 20
    case 9...15: 30
    case 16...25: 40
    default: 50
    }
    return travelClass == .first ? base * 2 : base
}

func total(of fares: Int...) -> Int {
    print("  inside total(of:), fares is \(type(of: fares)):", fares)
    var sum = 0
    for fare in fares {
        sum += fare
    }
    return sum
}

func balance(start: Int, fares: Int..., topUps: Int...) -> Int {
    var left = start
    for fare in fares {
        left -= fare
    }
    for topUp in topUps {
        left += topUp
    }
    return left
}

print("class left out:", fare(fromKm: 3, toKm: 11))
print("class given:", fare(fromKm: 3, toKm: 11, class: .first))
print("three fares:", total(of: 20, 30, 20))
print("no fares:", total())
print("balance:", balance(start: 50, fares: 20, 30, topUps: 100))

Output

class left out: 20
class given: 40
  inside total(of:), fares is Array<Int>: [20, 30, 20]
three fares: 70
  inside total(of:), fares is Array<Int>: []
no fares: 0
balance: 100

Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O defaults_variadic.swift -o defaults_variadic && ./defaults_variadic

Inside total(of:), fares is an Array<Int>. The three fares arrived as [20, 30, 20], and a call with no fares at all, total(), gave the function an empty array and returned 0. This table lists what each call handed over:

Call What the function received Result
fare(fromKm: 3, toKm: 11) travelClass: .standard, the default 20
fare(fromKm: 3, toKm: 11, class: .first) travelClass: .first 40
total(of: 20, 30, 20) fares: [20, 30, 20] 70
total() fares: [] 0
balance(start: 50, fares: 20, 30, topUps: 100) fares: [20, 30], topUps: [100] 100

Since Swift 5.4 a function may have several variadic parameters (SE-0284), as balance(start:fares:topUps:) does. One rule keeps such calls readable: the parameter after a variadic one must have a label, so topUps: marks where the fares end. Put parameters with defaults at the end of the list, as The Swift Programming Language advises. The API Design Guidelines add that an argument with a default should always keep its label: a call may leave it out, so it never takes part in the phrase that the rest of the call reads as.

Passing an array to a variadic parameter

With let fares = [20, 30, 20], the call total(of: fares) does not compile: a variadic parameter takes separate values, never an array, as the session below shows. When callers hold arrays, add a version of the function that takes [Int], and let the variadic version pass its own fares to it.

array_to_variadic.swift: an array where separate values belong Swift · array_to_variadic.swift
// array_to_variadic.swift: an array passed where separate values are expected.
func total(of fares: Int...) -> Int {
    var sum = 0
    for fare in fares {
        sum += fare
    }
    return sum
}

let fares = [20, 30, 20]
print(total(of: fares))
Compiling array_to_variadic.swift Python · variadic_session.py
from session import run, version

# array_to_variadic.swift passes an array to total(of:), whose parameter is variadic.
version()
run("""
!swiftc array_to_variadic.swift -o array_to_variadic
""")

Output

# Apple Swift version 6.4 (swiftlang-6.4.0.34.1)
$ swiftc array_to_variadic.swift -o array_to_variadic
array_to_variadic.swift:11:17: error: cannot pass array of type '[Int]' as variadic arguments of type 'Int'
 9 | 
10 | let fares = [20, 30, 20]
11 | print(total(of: fares))
   |                 `- error: cannot pass array of type '[Int]' as variadic arguments of type 'Int'
12 | 
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 variadic_session.py

Checkpoint

Check: Default values and variadic parameters

  1. Question 1 of 2 With the fare function of defaults_variadic.swift, class travelClass: TravelClass = .standard, what does fare(fromKm: 20, toKm: 4) return?

    Type a number.

    A hint

    Work out the distance first, then think about the class the call leaves out.

    Show the answer: Question 1 of 2

    Answer: 40 rupees

    abs(4 - 20) is 16 km, which falls in 16...25, a base fare of 40. The call leaves the class out, so the default .standard is used and the fare is not doubled: 40.

  2. Question 2 of 2 Inside func total(of fares: Int...), what is the type of fares?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: [Int], an array of the values the call gave

    A variadic parameter takes zero or more separate values, and the function receives them as an array of the parameter's type, here [Int]. A call with none gives an empty array.

A function returns one value, a tuple or nothing

distance(fromKm:toKm:) has one line in its body, abs(end - start), and no return. When the whole body of a function is a single expression, the function returns that expression: closures always had this shorthand, and functions gained it in Swift 5.1 (SE-0255). farePerKm(fare:km:) and isPeakHour(_:) in labels.swift are written the same way. This program returns four kinds of result:

returns.swift: one value, a tuple, an optional tuple and nothing Swift · returns.swift
// returns.swift: one value, a tuple, an optional tuple, and nothing at all.
func distance(fromKm start: Int, toKm end: Int) -> Int {
    abs(end - start)
}

func split(_ total: Int, among people: Int) -> (each: Int, left: Int) {
    (total / people, total % people)
}

func cheapestAndDearest(_ fares: [Int]) -> (cheapest: Int, dearest: Int)? {
    guard var cheapest = fares.first else {
        return nil
    }
    var dearest = cheapest
    for fare in fares {
        cheapest = min(cheapest, fare)
        dearest = max(dearest, fare)
    }
    return (cheapest, dearest)
}

func printTicket(fromKm start: Int, toKm end: Int) {
    print("ticket: km \(start) to km \(end)")
}

print("distance:", distance(fromKm: 3, toKm: 11))
let share = split(100, among: 3)
print("each pays", share.each, "and", share.left, "is left over")
if let range = cheapestAndDearest([30, 10, 50, 20]) {
    print("cheapest:", range.cheapest, "dearest:", range.dearest)
}
if cheapestAndDearest([]) == nil {
    print("no fares, no range: nil")
}
let nothing: Void = printTicket(fromKm: 3, toKm: 11)
print("printTicket returned:", nothing)

Output

distance: 8
each pays 33 and 1 is left over
cheapest: 10 dearest: 50
no fares, no range: nil
ticket: km 3 to km 11
printTicket returned: ()

Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O returns.swift -o returns && ./returns

Declared result Call What the caller gets
-> Int distance(fromKm: 3, toKm: 11) 8
-> (each: Int, left: Int) split(100, among: 3) each 33 and left 1
-> (cheapest: Int, dearest: Int)? cheapestAndDearest([]) nil: no fares, no range
no -> at all printTicket(fromKm: 3, toKm: 11) (), the empty tuple

A tuple: A group of a few values of any types, written in brackets, such as (low: 98, high: 201). Its parts are read by label, by position (.0, .1) or by destructuring, and it needs no declaration. More in “Operators, ranges and tuples” returns a few values together without declaring a type for them, and its labels name each part where it is used: share.each and share.left. A ? after the tuple’s closing bracket makes the whole tuple optional, which says that sometimes there is no answer at all. An empty list has no cheapest fare, and nil says so, where (cheapest: 0, dearest: 0) would be a wrong answer that looks right. A function without -> returns Void, which The Swift Programming Language defines as the empty tuple, (), and that is what the program printed for nothing.

Swift warns when a call drops the value a function returns, because a dropped result is often a mistake. Mark a function @discardableResult when ignoring its result is normal, as for printReceipt(_:), whose job is to print, and whose total a caller may want or not. The standard library marks Array’s remove(at:) the same way. This session compiles and runs a program that ignores three results:

unused.swift: three results that nobody uses Swift · unused.swift
// unused.swift: results that nobody uses.
func fare(fromKm start: Int, toKm end: Int) -> Int {
    abs(end - start) <= 3 ? 10 : 20
}

@discardableResult
func printReceipt(_ fares: [Int]) -> Int {
    var sum = 0
    for fare in fares {
        sum += fare
    }
    print("receipt:", fares.count, "fares, total", sum)
    return sum
}

var stops = ["Churchgate", "Dadar", "Thane"]
stops.remove(at: 0)
fare(fromKm: 3, toKm: 11)
printReceipt([20, 30])
_ = fare(fromKm: 0, toKm: 2)
print("stops left:", stops)
Compiling and running unused.swift Python · unused_session.py
from session import run, version

# unused.swift ignores three results: remove(at:) and printReceipt(_:) are marked @discardableResult, fare is not.
version()
run("""
swiftc unused.swift -o unused
./unused
""")

Output

# Apple Swift version 6.4 (swiftlang-6.4.0.34.1)
$ swiftc unused.swift -o unused
unused.swift:18:1: warning: result of call to 'fare(fromKm:toKm:)' is unused [#NoUsage]
16 | var stops = ["Churchgate", "Dadar", "Thane"]
17 | stops.remove(at: 0)
18 | fare(fromKm: 3, toKm: 11)
   | `- warning: result of call to 'fare(fromKm:toKm:)' is unused [#NoUsage]
19 | printReceipt([20, 30])
20 | _ = fare(fromKm: 0, toKm: 2)

[#NoUsage]: <https://docs.swift.org/compiler/documentation/diagnostics/no-usage>
$ ./unused
receipt: 2 fares, total 50
stops left: ["Dadar", "Thane"]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 unused_session.py

Only the call of fare(fromKm:toKm:) drew a warning, and the program still compiled and ran. The line _ = fare(fromKm: 0, toKm: 2) drops a result on purpose, and says so to the compiler and to the next reader.

Key idea

Return a plain value when there is always one answer, a tuple for a few named answers, an optional when there may be no answer, and nothing when the work of the function is its effect.

Checkpoint

Check: Return values, tuples and Void

  1. Question 1 of 2 Which declaration compiles?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: func double(_ fare: Int) -> Int { fare * 2 }

    Only a body that is one expression of the right type returns implicitly. A declaration returns nothing, and an expression of type () cannot stand for an Int.

  2. Question 2 of 2 Why does cheapestAndDearest(_:) return (cheapest: Int, dearest: Int)? rather than (cheapest: Int?, dearest: Int?)?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: An empty list has neither value and any other list has both, so one optional for the whole tuple says exactly that

    Put the ? where the absence is. For an empty list there is no range at all, so the whole tuple is nil; for any other list both values exist.

An inout parameter changes a variable of the caller

Parameters are constants: a plain fare: Int can be read in the body but not changed. Marking the parameter inout lets the function change the caller’s variable, and the call says so with &: applyDiscount(&fare, percent: 10) turned 40 into 36. The & is there so that whoever reads the call sees which variables it may change.

inout.swift: a discount, a move between two cards and swap Swift · inout.swift
// inout.swift: functions that change the caller's variables.
func applyDiscount(_ fare: inout Int, percent: Int) {
    fare -= fare * percent / 100
}

func move(_ amount: Int, from source: inout Int, to target: inout Int) {
    source -= amount
    target += amount
}

var fare = 40
applyDiscount(&fare, percent: 10)
print("fare after 10 percent off:", fare)

var ashaCard = 200
var raviCard = 30
move(50, from: &ashaCard, to: &raviCard)
print("Asha's card:", ashaCard, "Ravi's card:", raviCard)

swap(&ashaCard, &raviCard)
print("after swap:", ashaCard, raviCard)

Output

fare after 10 percent off: 36
Asha's card: 150 Ravi's card: 80
after swap: 80 150

Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O inout.swift -o inout && ./inout

The Swift Programming Language describes an in-out parameter: A parameter marked inout. The function may change it, and its value is written back to the caller's variable when the function returns. The call passes a variable with &, as in applyDiscount(&fare, percent: 10). as copy-in copy-out: the call copies the argument’s value into the parameter, the body changes the copy, and when the function returns, the copy is written back to the caller’s variable. The compiler may use the variable’s own memory instead, as an optimisation, so write code that is correct under the copy model. Step through move(50, from: &ashaCard, to: &raviCard) in that model:

Interactive · step by step move(50, from: &ashaCard, to: &raviCard), copied in and out Step through the call in the copy-in copy-out model. The left column is the caller's variables; the right column is the parameters inside move.
move(50, from: &ashaCard, to: &raviCard), copied in and out, step 5 of 5. Showing ashaCard (150), raviCard (80), source (source = 150), target (target = 80). In focus: source, target.&ashaCard&raviCardashaCard150raviCard80sourcesource = 150targettarget = 80same variable twice: refused

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. Before the call. ashaCard holds 200 and raviCard holds 30. The call names both with &, so a reader sees which variables move may change.
  2. Copied in. When move is called, each argument's value is copied into its parameter: source starts at 200 and target at 30.
  3. The body changes the copies. source -= 50 makes source 150 and target += 50 makes target 80. The caller's variables do not change yet in this model.
  4. Copied out. When move returns, each parameter's value is written back to its variable: ashaCard becomes 150 and raviCard 80, as inout.swift printed.
  5. One card in both places. move(50, from: &card, to: &card) would copy 200 into both parameters, end with 150 and 250, and write both back to card, so the result would hang on which write came last. Swift does not compile the call.

Only a variable can be an inout argument: not a constant, not a literal, and never without &. The standard library’s swap(_:_:) is a function like move, with two inout parameters. One variable cannot fill both parameters of one call, though. In the copy model, move(50, from: &card, to: &card) would start both parameters at 200, end them at 150 and 250, and write both back to card, so the result would depend on which write came last. An inout parameter has Swift's rule that while code changes a variable, no other access to that variable may overlap in time. An inout parameter's access lasts for the whole call, so a second inout argument that is the same variable would overlap with it. to its variable for the whole call, and Swift refuses the call at compile time. This session compiles both mistakes:

The two files the session compiles

no_ampersand.swift

// no_ampersand.swift: an inout argument without &, and a constant passed as one.
func applyDiscount(_ fare: inout Int, percent: Int) {
    fare -= fare * percent / 100
}

var fare = 40
applyDiscount(fare, percent: 10)
let dayPass = 120
applyDiscount(&dayPass, percent: 10)

aliasing.swift

// aliasing.swift: one variable passed to both inout parameters.
func move(_ amount: Int, from source: inout Int, to target: inout Int) {
    source -= amount
    target += amount
}

var card = 200
move(50, from: &card, to: &card)
print(card)
Compiling no_ampersand.swift and aliasing.swift Python · inout_session.py
from session import run, version

# no_ampersand.swift leaves out the & and passes a constant; aliasing.swift passes one variable to both inout parameters.
version()
run("""
!swiftc no_ampersand.swift -o no_ampersand
!swiftc aliasing.swift -o aliasing
""")

Output

# Apple Swift version 6.4 (swiftlang-6.4.0.34.1)
$ swiftc no_ampersand.swift -o no_ampersand
no_ampersand.swift:7:15: error: passing value of type 'Int' to an inout parameter requires explicit '&'
 5 | 
 6 | var fare = 40
 7 | applyDiscount(fare, percent: 10)
   |               `- error: passing value of type 'Int' to an inout parameter requires explicit '&'
 8 | let dayPass = 120
 9 | applyDiscount(&dayPass, percent: 10)

no_ampersand.swift:9:15: error: cannot pass immutable value as inout argument: 'dayPass' is a 'let' constant
 6 | var fare = 40
 7 | applyDiscount(fare, percent: 10)
 8 | let dayPass = 120
   | `- note: change 'let' to 'var' to make it mutable
 9 | applyDiscount(&dayPass, percent: 10)
   |               `- error: cannot pass immutable value as inout argument: 'dayPass' is a 'let' constant
10 | 
[exit status 1]
$ swiftc aliasing.swift -o aliasing
aliasing.swift:8:27: error: inout arguments are not allowed to alias each other
 6 | 
 7 | var card = 200
 8 | move(50, from: &card, to: &card)
   |                |          `- error: inout arguments are not allowed to alias each other
   |                `- note: previous aliasing argument
 9 | print(card)
10 | 
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 inout_session.py

The compiler names each mistake in its own words: the & is required, a let constant cannot be changed, and “inout arguments are not allowed to alias each other”, where to alias means to name the same memory.

Return a new value when one value comes back

func discounted(_ fare: Int, percent: Int) -> Int is easier to read and to test than an inout version, because let price = discounted(fare, percent: 10) shows what changes and keeps fare as it was. Keep inout for changing a value in place, as swap(_:_:) does.

Checkpoint

Check: inout parameters and exclusive access

  1. Question 1 of 2 var fare = 50, then applyDiscount(&fare, percent: 20), whose body is fare -= fare * percent / 100. What is fare after the call?

    Type a number.

    Show the answer: Question 1 of 2

    Answer: 40 rupees

    50 * 20 / 100 is 10, and 50 - 10 is 40. The parameter is inout, so the change is written back to the caller's fare when the function returns.

  2. Question 2 of 2 Why does move(50, from: &card, to: &card) not compile, when move(50, from: &ashaCard, to: &raviCard) does?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Each inout parameter needs exclusive access to its variable for the whole call, and here both would use card

    inout arguments are not allowed to alias each other. Two different variables are fine; the same variable twice would give the call two overlapping write accesses to one piece of memory.

Labels and types tell overloads apart, and functions can nest

overloads.swift declares three functions called fare: one takes fromKm: and toKm:, one takes a route and one takes a list of routes. Swift tells them apart by their full names, written with the labels, such as fare(fromKm:toKm:), the form the warning in the last section printed. Where the labels match, the types of the arguments decide. Declaring functions like this is called overloading: Declaring several functions with the same base name that differ in their argument labels or parameter types, such as fare(fromKm:toKm:) and fare(for:). Each call runs the one its labels and argument types fit., and the API Design Guidelines allow it when the functions share one basic meaning, as these do: each works out a fare. Route is a type alias: A second name for an existing type, declared with typealias, such as typealias Paise = Int. The alias and the original are the same type, so values of the two mix freely. More in “Operators, ranges and tuples” for a tuple of two distances.

overloads.swift: three fare functions and one nested function Swift · overloads.swift
// overloads.swift: one base name, three functions, told apart by their labels and types.
typealias Route = (fromKm: Int, toKm: Int)

func fare(fromKm start: Int, toKm end: Int) -> Int {
    switch abs(end - start) {
    case 0...3: 10
    case 4...8: 20
    case 9...15: 30
    case 16...25: 40
    default: 50
    }
}

func fare(for route: Route) -> Int {
    fare(fromKm: route.fromKm, toKm: route.toKm)
}

func fare(for routes: [Route]) -> Int {
    var sum = 0
    for route in routes {
        sum += fare(for: route)
    }
    return sum
}

// A nested function: add(_:) is visible only inside report(_:), and it changes report's lines.
func report(_ routes: [Route]) -> [String] {
    var lines: [String] = []
    func add(_ route: Route) {
        lines.append("km \(route.fromKm) to \(route.toKm): \(fare(for: route))")
    }
    for route in routes {
        add(route)
    }
    lines.append("day total: \(fare(for: routes))")
    return lines
}

let today: [Route] = [(fromKm: 3, toKm: 11), (fromKm: 11, toKm: 3), (fromKm: 0, toKm: 2)]
print("fare(fromKm:toKm:)", fare(fromKm: 3, toKm: 11))
print("fare(for:) with a Route", fare(for: (fromKm: 0, toKm: 2)))
print("fare(for:) with [Route]", fare(for: today))
for line in report(today) {
    print(line)
}

Output

fare(fromKm:toKm:) 20
fare(for:) with a Route 10
fare(for:) with [Route] 50
km 3 to 11: 20
km 11 to 3: 20
km 0 to 2: 10
day total: 50

Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O overloads.swift -o overloads && ./overloads

Step through how the call fare(for: today) finds its function among the three:

Interactive · step by step Which fare runs for fare(for:) with three routes? Step through the call from overloads.swift. The right column holds the three functions named fare; each step rules one out or runs one.
Which fare runs for fare(for:) with three routes?, step 5 of 5. Showing fare(for: today), fare(for: route), fare(fromKm:toKm:) (the fare table: 20, 20, 10), fare(for: Route) (fits each single route), fare(for: [Route]). In focus: fare(fromKm:toKm:).runseach routerunsits two numbersfare(for: today)today: [Route], 3 routesfare(for: route)route: Routefare(fromKm:toKm:)the fare table: 20, 20, 10fare(for: Route)fits each single routefare(for: [Route])a list of routes

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. Three functions, one name. The call fare(for: today) names the base name fare and one label, for. overloads.swift declares three functions with that base name.
  2. The labels rule one out. fare(fromKm:toKm:) has the labels fromKm and toKm, not for, so it cannot be this call. Two functions are left.
  3. The type rules one out. Both fare(for:) functions have the label for. One takes a single Route and today is an array of routes, so the argument's type leaves only fare(for: [Route]).
  4. The list version runs. fare(for: [Route]) runs. Its loop calls fare(for: route) with one route at a time, and for that call the Route version is the one that fits.
  5. Down to the fare table. fare(for: Route) passes the route's two numbers to fare(fromKm:toKm:). The three routes cost 20, 20 and 10, so the call returns 50, as overloads.swift printed.

The labels ruled out fare(fromKm:toKm:), and the type of today, an array, ruled out the version that takes one route. The guidelines also advise against functions that differ only in the type they return, because type inference cannot always tell such calls apart.

add(_:) is declared inside report(_:): it is a nested function: A function declared inside the body of another function. Only the enclosing function can call it, and it can read and change that function's variables.. The rest of the program cannot see it, and only report(_:) can call it. It also reads and changes a variable of the function around it: each call of add appended a line to report’s lines. A function that uses the variables of the place where it was written is said to capture them, and capturing is what the next lesson’s closures build on.

Checkpoint

Check: Overloads and nested functions

  1. Question 1 of 2 With the three fare functions of overloads.swift, which one does the call fare(for: (fromKm: 0, toKm: 2)) pick?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: fare(for: Route), which then calls fare(fromKm:toKm:)

    Labels first, then types: for leaves the two fare(for:) functions, and a single Route argument leaves fare(for: Route), which passes the two numbers on to fare(fromKm:toKm:). overloads.swift printed 10.

  2. Question 2 of 2 Where can the nested function add(_:) of overloads.swift be called?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Only inside report(_:), the function that declares it

    Nested functions are hidden from the outside, but their enclosing function can call them, and they can use its variables: add appended to report's lines.

Name a function so that its call reads as a phrase

The API Design Guidelines put one goal first: clarity at the point of use, where a function is called, which matters more than brevity. Every word of a name should carry information, and parameters are named for their role, not for their type. Three of its rules decide most labels in this lesson. An argument that completes a phrase that starts with a preposition gets a label: fare(fromKm:toKm:), total(of:). An argument that continues the function’s name as a phrase gets none: isPeakHour(9). Every other argument is labelled: percent:. Here are calls before and after those rules:

Before After Guideline
fare(3, 11) fare(fromKm: 3, toKm: 11) Include every word needed to avoid ambiguity
totalFareAmount(fares: 20, 30) total(of: 20, 30) Omit needless words
applyDiscount(&fare, 10) applyDiscount(&fare, percent: 10) Label the other arguments, by their role
isPeakHour(hour: 9) isPeakHour(9) No label where the call already reads as a phrase
discount(fare, percent: 10), returning a new fare discounted(fare, percent: 10) A noun or “-ed” form for a function without side effects

The last row is about effects. The guidelines name a function that changes something with an imperative verb, applyDiscount, and one that returns a new value instead with a noun or an “-ed” form, discounted, the way the standard library pairs sort() with sorted(). A reader then knows from the name alone whether fare changes.

Key idea

Judge a name by its call, not by its declaration: the name and its labels are right when the call reads as a short, exact phrase.

Checkpoint

Check: Names that read as phrases

  1. Question 1 of 2 A function sends a fare receipt to a rider's phone number. Which declaration gives the clearest call?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: func sendReceipt(to phone: String), called as sendReceipt(to: riderPhone)

    Read each call aloud. sendReceipt(to: riderPhone) is a short, exact phrase, which is what the API Design Guidelines ask of a name and its labels.

  2. Question 2 of 2 One function lowers a fare in place; another returns a lowered copy and leaves the fare alone. Which pair of names follows the guidelines?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: applyDiscount(_:percent:) changes in place, discounted(_:percent:) returns a copy

    Name functions by their side effects: a verb for the one that changes something, a noun or an -ed form for the one that returns a new value.

Practise: a metro card in five functions

Write five functions for a metro card, so that each call reads as a phrase:

  • fare(fromKm:toKm:class:), this lesson’s fare table, with .standard as the default class;
  • total(of:), the sum of any number of fares, given one by one;
  • discounted(_:percent:), a fare after a percentage off, returned as a new value;
  • topUp(_:by:limit:), which adds money to a card balance in place, never past a limit of 1,000 unless the call gives another, and returns how much it added, a result that callers may ignore;
  • longestTrip(in:), the route that covers the most kilometres, or nil when there are no routes.

Write and test yours before reading this version:

practice.swift: one way to write it Swift · practice.swift
// practice.swift: one way to write the five functions of the metro card.
enum TravelClass {
    case standard, first
}

typealias Route = (fromKm: Int, toKm: Int)

func fare(fromKm start: Int, toKm end: Int, class travelClass: TravelClass = .standard) -> Int {
    let base = switch abs(end - start) {
    case 0...3: 10
    case 4...8: 20
    case 9...15: 30
    case 16...25: 40
    default: 50
    }
    return travelClass == .first ? base * 2 : base
}

func total(of fares: Int...) -> Int {
    var sum = 0
    for fare in fares {
        sum += fare
    }
    return sum
}

func discounted(_ fare: Int, percent: Int) -> Int {
    fare - fare * percent / 100
}

@discardableResult
func topUp(_ balance: inout Int, by amount: Int, limit: Int = 1000) -> Int {
    let added = max(0, min(amount, limit - balance))
    balance += added
    return added
}

func longestTrip(in routes: [Route]) -> Route? {
    guard var longest = routes.first else {
        return nil
    }
    for route in routes where abs(route.toKm - route.fromKm) > abs(longest.toKm - longest.fromKm) {
        longest = route
    }
    return longest
}

let morning = fare(fromKm: 3, toKm: 11)
let evening = fare(fromKm: 11, toKm: 3, class: .first)
let errand = fare(fromKm: 0, toKm: 2)
let outing = fare(fromKm: 5, toKm: 30)
print("fares:", morning, evening, errand, outing)
let sum = total(of: morning, evening, errand, outing)
print("total:", sum, "with 20 percent off:", discounted(sum, percent: 20))

var balance = 950
let added = topUp(&balance, by: 100)
print("added", added, "so the balance is", balance)
topUp(&balance, by: 100)
print("at the limit, the balance stays", balance)

let today: [Route] = [(fromKm: 3, toKm: 11), (fromKm: 0, toKm: 2), (fromKm: 5, toKm: 30)]
if let longest = longestTrip(in: today) {
    print("longest trip: km \(longest.fromKm) to \(longest.toKm)")
}
print("no trips:", longestTrip(in: []) == nil ? "nil" : "a trip")

Output

fares: 20 40 10 40
total: 110 with 20 percent off: 88
added 50 so the balance is 1000
at the limit, the balance stays 1000
longest trip: km 5 to 30
no trips: nil

Recorded with Apple Swift 6.4 (swiftlang-6.4.0.34.1) on macOS 26 arm64. To run it yourself: swiftc -O practice.swift -o practice && ./practice

topUp(_:by:limit:) is @discardableResult, so its second call needs no _ =, and it adds only 50 of the 100 asked for, because the card stops at 1,000. longestTrip(in:) returns an optional tuple, nil for an empty list. Then work through the practice round: it starts with the idea you know least and moves up a level after each right answer.

Practice that adapts to you

Practise this lesson

12 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 12 A parameter is declared with a single name, as in func greet(rider: String). What is its argument label?

    Choose one answer.

    Show the answer: Question 1 of 12

    Answer: rider, the same as its name

    One name serves as both the label and the parameter name. Write two names to make them differ, or _ to remove the label.

  2. Question 2 of 12 You want calls to read stations(from: "Dadar", to: "Thane") while the body works with start and end. Which declaration does it?

    Choose one answer.

    Show the answer: Question 2 of 12

    Answer: func stations(from start: String, to end: String) -> [String]

    Write the label first and the parameter name second. One name would make the body use the label's word as well.

  3. Question 3 of 12 Where do the guidelines advise putting parameters that have default values?

    Choose one answer.

    Show the answer: Question 3 of 12

    Answer: Towards the end of the parameter list

    Put the parameters without defaults first and those with defaults towards the end, each with a label.

  4. Question 4 of 12 Which declaration does not compile?

    Choose one answer.

    Show the answer: Question 4 of 12

    Answer: func balance(fares: Int..., _ topUps: Int...) -> Int

    Without a label after a variadic parameter, nothing in a call would show where the first list of values ends and the next argument begins.

  5. Question 5 of 12 What does a function declared without -> return?

    Choose one answer.

    Show the answer: Question 5 of 12

    Answer: Void, which is the empty tuple ()

    Functions without a defined return type return a value of type Void, an empty tuple written ().

  6. Question 6 of 12 Which line of this program draws a warning when it is compiled?

    Read the code, then choose one answer.

    var stops = ["Dadar", "Thane"]
    func fare(km: Int) -> Int { km <= 3 ? 10 : 20 }
    stops.remove(at: 0)    // line A
    fare(km: 8)            // line B
    _ = fare(km: 2)        // line C
    Show the answer: Question 6 of 12

    Answer: Line B only

    Swift warns when a call drops a result, unless the function is marked @discardableResult or the call assigns the result to _.

  7. Question 7 of 12 How does a call pass the variable fare to func applyDiscount(_ fare: inout Int, percent: Int)?

    Choose one answer.

    Show the answer: Question 7 of 12

    Answer: applyDiscount(&fare, percent: 10)

    The & at the call site is required, and it shows a reader which variables the call may change.

  8. Question 8 of 12 var fares = [20, 30], then swap(&fares[0], &fares[1]). What happens?

    Choose one answer.

    Show the answer: Question 8 of 12

    Answer: It does not compile, because both inout arguments access the same array, fares

    Exclusive access applies to the whole array: changing one element is an access to fares. Use fares.swapAt(0, 1), which the error message itself points to.

  9. Question 9 of 12 What is the full name of func fare(for routes: [Route]) -> Int?

    Choose one answer.

    Show the answer: Question 9 of 12

    Answer: fare(for:)

    Swift writes a function's name with its labels, as the compiler did in "result of call to fare(fromKm:toKm:) is unused". Two fare(for:) functions with that name still differ by type.

  10. Question 10 of 12 Which pair of declarations cannot both be in one program?

    Choose one answer.

    Show the answer: Question 10 of 12

    Answer: fare(fromKm a: Int, toKm b: Int) -> Int and fare(fromKm start: Int, toKm end: Int) -> Int

    Parameter names belong to the body and play no part in telling functions apart. Labels and parameter types do.

  11. Question 11 of 12 What do the Swift API Design Guidelines call the most important goal of a name?

    Choose one answer.

    Show the answer: Question 11 of 12

    Answer: Clarity at the point of use, where the function is called

    A function is declared once and called many times, so its name is judged at the call.

  12. Question 12 of 12 func add(_ a: Int, _ b: Int) adds a top-up amount to a card. Which rename follows the guidelines best?

    Choose one answer.

    Show the answer: Question 12 of 12

    Answer: func topUp(_ balance: inout Int, by amount: Int), called as topUp(&balance, by: 100)

    Name by role, label what a phrase needs, and omit what the call already says: topUp(&balance, by: 100).

Interview questions

Warm-up: what is the difference between an argument label and a parameter name in Swift?

The argument label is the name a caller writes before an argument, and the parameter name is the name the function’s body uses for it. In func fare(fromKm start: Int, toKm end: Int) the labels are fromKm and toKm, so the call is fare(fromKm: 3, toKm: 11), while the body reads start and end. Writing one name makes it both, and _ as the label removes it from calls. Labels are part of the function’s full name, fare(fromKm:toKm:), so they also tell overloads apart. The two names serve different readers: the label makes the call read as a phrase, and the parameter name keeps the body short and clear. A call must use the labels exactly, in order; using the parameter names instead is a compile-time error.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A wallet app has func transfer(_ amount: Int, from source: inout Int, to target: inout Int). After a refactor, the call transfer(500, from: &balances[0], to: &balances[1]) no longer compiles, although the two indices differ. Explain the error and redesign the API so that the transfer compiles and stays correct.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A fare library exposes func fare(_ a: Int, _ b: Int, _ c: Bool, _ d: Bool) -> Int, where c means first class and d means peak hours. Calls such as fare(3, 11, true, false) keep causing billing bugs. Redesign the API with Swift's labels, defaults and types, and explain every choice.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Each parameter has an argument label, which the call writes, and a parameter name, which the body uses; one name serves as both, and _ removes the label.
  • A default value lets a call leave an argument out, and a variadic parameter takes any number of separate values, which the body receives as an array.
  • A single-expression body returns its value without return; a tuple returns a few named values, an optional tuple may return none, and a function without -> returns Void.
  • An inout parameter writes its changes back to the caller’s variable, the call marks it with &, and one variable cannot be passed to two inout parameters of the same call.
  • Overloads share a base name and differ in their labels or parameter types, and a nested function is visible only inside the function that declares it.
  • Names and labels are right when the call reads as a short, exact phrase, as the Swift API Design Guidelines advise.

Terms in this lesson

argument label
The name a call writes before an argument, as fromKm in fare(fromKm: 3, toKm: 11). It may differ from the parameter name the function's body uses, and an underscore in its place means the call writes no label.
default value
A value written after a parameter's type, as in class travelClass: TravelClass = .standard. A call may leave that argument out, and the function then uses the default.
in-out parameter
A parameter marked inout. The function may change it, and its value is written back to the caller's variable when the function returns. The call passes a variable with &, as in applyDiscount(&fare, percent: 10).
nested function
A function declared inside the body of another function. Only the enclosing function can call it, and it can read and change that function's variables.
overloading
Declaring several functions with the same base name that differ in their argument labels or parameter types, such as fare(fromKm:toKm:) and fare(for:). Each call runs the one its labels and argument types fit.
variadic parameter
A parameter written with three dots after its type, as in fares: Int..., that takes zero or more separate values. The function receives them as an array, here [Int]; an array cannot be passed in their place.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.