Swift In-Out Parameters (inout): Syntax, Examples, and Rules

Swift inout parameters let a function temporarily modify a value that was passed in by the caller. This is useful when a function needs to change an existing variable directly instead of returning a new value. In this article, you will learn how inout works, when to use it, how to call functions with &, and which errors and edge cases often confuse beginners.

Quick answer: In Swift, mark a function parameter with inout when the function should change the caller's variable. When calling that function, you must pass a variable with &, because constants, literals, and temporary values cannot be used as inout arguments.

Difficulty: Beginner

Helpful to know first: You'll understand this better if you already know basic Swift function syntax, variables declared with let and var, and how functions can return values.

1. What Is In-Out Parameters (inout)?

An inout parameter is a function parameter that allows the function to modify the original value supplied by the caller.

Normally, Swift function parameters are constants inside the function. That means you can read them, but you cannot assign a new value to them. The inout keyword changes that behavior for a specific parameter.

Although people sometimes describe inout as "pass by reference," Swift actually uses a copy-in, copy-out model with compiler optimizations. For everyday use, the practical idea is simple: the function can update the caller's variable.

2. Why In-Out Parameters (inout) Matters

inout matters because some tasks are easier to express when a function directly updates an existing variable.

Without inout, you often need to return a new value and then assign it back yourself. That is usually fine, and sometimes better, but there are cases where changing the original variable is clearer.

Real situations where inout helps include:

At the same time, inout should not be overused. If a function only needs to calculate and return a result, a normal return value is often simpler and easier to read.

3. Basic Syntax or Core Idea

The basic syntax has two parts:

Declaring an inout parameter

Here is the smallest useful example. The function increases a number by one.

func increment(value: inout Int) {
value += 1
}

var count = 5
increment(value: &count)
print(count) // 6

The parameter value is marked as inout, so the function is allowed to modify it. In the call, &count tells Swift to pass the variable as an inout argument.

What each part means

In the example above:

Swift inout vs returning a value

This topic is commonly confused with simply returning a new value. Both approaches are valid, but they communicate different intent.

func incremented(value: Int) -> Int {
return value + 1
}

var score = 10
score = incremented(value: score)

This version does not modify the argument directly. It returns a new value, and the caller decides what to do with it.

Use a return value when the function is producing a result. Use inout when the function's purpose is specifically to update an existing variable.

4. Step-by-Step Examples

Example 1: Increase a player's score

This example shows a practical numeric update.

func addBonus(to score: inout Int, points: Int) {
score += points
}

var playerScore = 120
addBonus(to: &playerScore, points: 30)
print(playerScore) // 150

The function updates the original score variable directly. This is useful when your intent is clearly "change this stored value."

Example 2: Swap two values

One classic use of inout is swapping values.

func swapNumbers(_ a: inout Int, _ b: inout Int) {
let temporary = a
a = b
b = temporary
}

var left = 3
var right = 9

swapNumbers(&left, &right)
print(left, right) // 9 3

After the function call, the values have exchanged places because both caller variables were passed as inout arguments.

Example 3: Clamp a number into a valid range

This pattern is useful when user input or calculations may produce invalid values.

func clamp(_ value: inout Int, min: Int, max: Int) {
if value < min {
value = min
} else if value > max {
value = max
}
}

var volume = 130
clamp(&volume, min: 0, max: 100)
print(volume) // 100

The function directly corrects the existing value, which can be clearer than returning a replacement when the goal is validation or normalization.

Example 4: Update two related values together

inout becomes especially useful when a function needs to change more than one variable.

func applyPurchase(price: Int, balance: inout Int, itemsBought: inout Int) {
balance -= price
itemsBought += 1
}

var wallet = 200
var purchases = 0

applyPurchase(price: 45, balance: &wallet, itemsBought: &purchases)
print(wallet) // 155
print(purchases) // 1

This avoids returning a tuple just to write values back into existing variables.

5. Practical Use Cases

6. Common Mistakes

Mistake 1: Forgetting the & when calling the function

When a function expects an inout parameter, the caller must pass a variable with &. This is part of the syntax, not an optional extra.

Problem: This call passes a normal argument instead of an inout argument, so Swift reports that the parameter requires &.

func doubleValue(_ number: inout Int) {
number *= 2
}

var points = 8
doubleValue(points)

Fix: Add & before the variable when calling the function.

func doubleValue(_ number: inout Int) {
number *= 2
}

var points = 8
doubleValue(&points)

The corrected version works because the argument is now passed as an inout value.

Mistake 2: Passing a constant or literal to an inout parameter

Only mutable variables can be passed to an inout parameter. Constants declared with let and literal values cannot be modified.

Problem: This code tries to pass values that cannot be changed, so Swift rejects them. You may see errors such as "Cannot pass immutable value as inout argument" or similar compiler messages.

func increase(_ value: inout Int) {
value += 1
}

let fixedNumber = 10
increase(&fixedNumber)

increase(&10)

Fix: Store the value in a mutable variable declared with var before passing it.

func increase(_ value: inout Int) {
value += 1
}

var number = 10
increase(&number)

The corrected version works because number is a mutable variable that the function is allowed to update.

Mistake 3: Trying to modify a normal parameter directly

Swift function parameters are constants by default. If you need to assign a new value to a parameter inside the function, you must either use inout or copy the value into a local variable.

Problem: This function attempts to reassign a regular parameter, which Swift does not allow. A common compiler message is "Left side of mutating operator isn't mutable: 'value' is a 'let' constant."

func addTen(value: Int) {
value += 10
}

Fix: If the caller's variable should change, use inout. Otherwise, create and return a new value.

func addTen(value: inout Int) {
value += 10
}

var total = 5
addTen(value: &total)

The corrected version works because the parameter is explicitly declared as mutable input from the caller.

Mistake 4: Passing the same variable to multiple inout parameters

Swift protects memory access carefully. If the same variable is passed to two inout parameters at the same time, the accesses overlap.

Problem: This creates conflicting read and write access to the same memory location. Swift may report an error such as "Overlapping accesses to 'value', but modification requires exclusive access."

func adjustBoth(_ a: inout Int, _ b: inout Int) {
a += 1
b += 1
}

var value = 10
adjustBoth(&value, &value)

Fix: Pass different variables, or redesign the function so it modifies one value at a time.

func adjustBoth(_ a: inout Int, _ b: inout Int) {
a += 1
b += 1
}

var first = 10
var second = 20
adjustBoth(&first, &second)

The corrected version works because each inout parameter now has exclusive access to a different variable.

7. Best Practices

Use inout only when mutation is the main purpose

Some functions are clearer when they return a value instead of changing an argument. Choose inout when in-place modification is the point of the function.

// Less preferred when you only need a computed result
func square(_ value: inout Int) {
value *= value
}

// Often clearer
func squared(_ value: Int) -> Int {
return value * value
}

This keeps function intent clearer: one version changes a variable, and the other computes a result.

Give inout parameters names that suggest change

Parameter labels can make mutating behavior easier to understand at the call site.

func reset(_ value: inout Int) {
value = 0
}

func reset(score: inout Int) {
score = 0
}

The second version reads more clearly because the label explains what is being changed.

Keep inout functions focused and predictable

A function that changes too many unrelated values can be hard to reason about. Prefer small, clear mutations.

// Harder to reason about
func process(_ score: inout Int, _ name: inout String, _ isActive: inout Bool) {
score += 10
name = name.uppercased()
isActive = true
}

// Clearer and more focused
func activate(_ flag: inout Bool) {
flag = true
}

Small functions are easier to test, reuse, and understand.

8. Limitations and Edge Cases

If your code feels awkward with many inout parameters, that is often a sign the function should return a result, use a tuple, or be redesigned around a type that manages its own state.

9. Practical Mini Project

This mini project builds a small score manager using several inout helper functions. It shows how in-place updates can make state changes readable.

func addPoints(_ score: inout Int, points: Int) {
score += points
}

func applyPenalty(_ score: inout Int, points: Int) {
score -= points
}

func clampToZero(_ score: inout Int) {
if score < 0 {
score = 0
}
}

var score = 50

addPoints(&score, points: 20)
applyPenalty(&score, points: 80)
clampToZero(&score)

print("Final score: \(score)") // Final score: 0

This example uses three focused functions. Each one performs one clear mutation, and the final result is easy to trace. That is a good style for inout: simple functions with obvious effects.

10. Key Points

11. Practice Exercise

Try this exercise to check your understanding of inout.

Expected output: If the starting price is 40 and the discount is 50, the final printed value should be 0.

Hint: Remember to mark the parameter with inout and use & at the call site.

func applyDiscount(price: inout Int, discount: Int) {
price -= discount

if price < 0 {
price = 0
}
}

var itemPrice = 40
applyDiscount(price: &itemPrice, discount: 50)
print(itemPrice) // 0

12. Final Summary

Swift inout parameters give functions a controlled way to modify a caller's variable. You use inout in the function definition and & when calling the function. This is especially useful for tasks like swapping values, adjusting counters, clamping numbers, or updating multiple related variables in one operation.

The most important things to remember are that only mutable variables can be passed as inout arguments, normal parameters are constant by default, and Swift enforces exclusive access rules to keep memory safe. If a function is really just computing a result, returning a value may be the clearer choice.

As a next step, it helps to learn how mutating methods work on structs and how Swift handles value types, because those concepts connect closely to how and why inout behaves the way it does.