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.
- inout is written before the parameter's type.
- The caller passes a variable using the & prefix.
- The function can change that variable's value.
- You can use inout only with variables, not constants or literals.
- It is often used for swapping, accumulating, normalizing, or updating multiple values in one call.
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:
- Swapping two values.
- Incrementing counters or totals.
- Clamping a number into a valid range.
- Updating multiple values in a single function call.
- Writing utility functions that adjust state in place.
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:
- In the function definition, mark the parameter as inout.
- In the function call, pass a variable using &.
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) // 6The 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:
- func increment(...) defines a function.
- value: inout Int means the function can change an Int variable.
- &count means "pass this variable so the function can update it."
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) // 150The 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 3After 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) // 100The 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) // 1This avoids returning a tuple just to write values back into existing variables.
5. Practical Use Cases
- Updating a game score, lives count, or timer directly.
- Normalizing user-entered numbers such as percentage, age, or quantity limits.
- Swapping two values in utility code or algorithm practice.
- Adjusting multiple pieces of state together, such as a balance and a transaction count.
- Writing helper functions that mutate a value in place for clearer intent.
- Working with custom types when a function should modify a caller-owned value instead of building a replacement.
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
- You must pass a mutable variable with &. Constants, literals, and expressions do not work.
- Swift enforces exclusive access to memory, so overlapping inout access can produce compile-time or runtime errors.
- inout does not mean unrestricted shared mutation. Swift still protects safety rules carefully.
- Using inout can make code less obvious if the function name does not clearly signal mutation.
- For many simple operations, returning a new value is easier to read than mutating in place.
- When working with computed properties or complex access patterns, Swift may reject certain inout uses because of exclusive access rules.
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: 0This 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
- inout lets a function modify the caller's variable.
- You declare inout in the parameter type and pass the argument with &.
- Only mutable variables declared with var can be passed as inout arguments.
- Normal function parameters are constants inside the function.
- inout is useful for direct updates, swaps, and coordinated state changes.
- Swift enforces exclusive memory access, so overlapping inout usage can fail.
- Sometimes returning a new value is clearer than mutating an argument.
11. Practice Exercise
Try this exercise to check your understanding of inout.
- Create a function named applyDiscount.
- The function should take an inout Int named price.
- It should also take an Int named discount.
- Subtract the discount from the price.
- If the result is less than 0, set the price to 0.
- Call the function with a variable price and print the final value.
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) // 012. 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.