Understanding inout Parameters in Swift - How They Work and When to Use Them

April 25, 2026

Swift is designed to be safe by default. Values are immutable unless explicitly marked mutable, and function parameters are constants by default. This prevents accidental side effects and makes code easier to reason about.

But real-world code isn’t always purely functional. Sometimes, you need to modify a value directly.

That’s where inout parameters come in.

This article explores inout in depth - what it is, how it works, when to use it, and (just as importantly) when not to.


What is an inout Parameter?

An inout parameter allows a function to modify the value of a variable passed into it.

By default, function parameters are immutable::

func increment(_ value: Int) {
    value += 1 // ❌ Error: 'value' is a constant
}

To allow mutation, you mark the parameter as inout:

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

And when calling the function, you pass the variable using &:

var number = 10
increment(&number)

print(number) // 11

That & is not just syntax - it’s a signal:

“This value will be mutated.”


How inout Works Under the Hood

At first glance, inout looks like pass-by-reference. But Swift doesn’t expose raw references like that.

Instead, it uses a model called copy-in copy-out:

  1. The value is copied when passed into the function
  2. The function modifies the local copy
  3. The modified value is written back when the function returns

This gives you the benefits of mutation while preserving Swift’s safety guarantees.

In practice, thanks to compiler optimizations, it often behaves like a reference but you should think of it as controlled mutation, not direct memory access.


Why inout Actually Matters

Most Swift developers either:

  • avoid inout completely, or
  • use it without thinking about the trade-offs

Both approaches miss the point.

inout exists for one reason:

To make mutation explicit, controlled, and intentional.

It Forces You to Be Honest About Mutation

Compare these two functions:

func updateBalance(_ balance: Int) -> Int
func updateBalance(_ balance: inout Int)

The first one looks pure. The second one makes a stronger promise:

“I will mutate what you give me.”

That’s not just implementation - it’s API design.

It Reduces Noise - When Used Correctly

balance = updateBalance(balance)

vs

updateBalance(&balance)

When mutation is the goal, inout is more direct and expressive.

It’s Not About Performance (Mostly)

A common misconception is that inout is primarily for performance.

It’s not.

Swift already has:

  • Copy-on-write
  • ARC optimizations
  • Efficient value semantics

The real value of inout is clarity of intent, not micro-optimization.


Basic Examples

Mutating a Value

func double(_ value: inout Int) {
    value *= 2
}

var x = 5
double(&x)

print(x) // x = 10

This is straightforward: the function directly mutates the input.

Swapping Values

func swapValues(_ a: inout Int, _ b: inout Int) {
    let temp = a
    a = b
    b = temp
}

var x = 10
var y = 20

swapValues(&x, &y)

print(x) // x = 20
print(y) // y = 10

This is a classic use case where returning values would be awkward.

Working with Collections

func removeNegatives(from array: inout [Int]) {
    array.removeAll { $0 < 0 }
}

var numbers = [3, -1, 5, -2, 7]
removeNegatives(from: &numbers)

print(numbers) // [3, 5, 7]

This keeps mutation localized and avoids unnecessary intermediate arrays.


inout vs Return Values: This Is a Design Decision

This isn’t just syntax - it’s philosophy.

Use Return Values for Transformation

func normalized(_ value: Double) -> Double

This is pure. Input → Output. No surprises.

This should be your default.

Use inout for Mutation

func normalize(_ value: inout Double)

This says:

“This function changes existing state.”

That’s a stronger contract.

Rule of Thumb

  • If it could be pure → don’t use inout
  • If mutation is the point → use inout

When in doubt, return a value.


When You Should Use inout

Use inout when mutation is the purpose - not a side effect.

Good use cases:

  • Swapping values
  • Updating shared state
  • Mutating collections in-place
  • Avoiding awkward tuple returns

Example:

func applyDiscount(_ price: inout Double, percentage: Double) {
    price -= price * percentage / 100
}

When You Should Avoid inout

Avoid it when:

The Function Is Conceptually Pure

func squared(_ value: Int) -> Int

Making this inout would hurt clarity.

You Need Composability

value = normalize(value)

This supports chaining. inout does not.

It Hides Side Effects

func process(_ data: inout Data)

What does "process" do?

If mutation isn’t obvious, don’t use inout.


Important Rules and Constraints

Must Use &

increment(&number) // ✅
increment(number)  // ❌

Only Variables Allowed

let value = 10
increment(&value) // ❌

No Conflicting Access

var x = 10

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

modify(&x, &x) // ❌

Swift prevents unsafe memory access.

Cannot Escape Scope

func update(_ value: inout Int) {
    DispatchQueue.global().async {
        value = 5 // ❌ Not allowed
    }
}

inout only lives within the function’s execution.


Common Mistakes with inout

inout looks simple but it’s easy to misuse if you treat it as a shortcut instead of a design tool.

1. Using inout When a Return Value Is Better

func increment(_ value: inout Int)

Better:

func increment(_ value: Int) -> Int

If it’s a transformation, don’t force mutation.

2. Using It to Save a Line of Code

update(&value)

vs

value = update(value)

This is not an improvement.

Clarity > fewer lines.

3. Hiding Significant Mutation

func process(_ data: inout [Int])

If it clears the array, the name should say so.

4. Passing Same Variable Twice

combine(&value, &value) // ❌

Swift blocks this for safety.

5. Expecting True Reference Behavior

It’s not raw reference - it’s controlled mutation via copy-in copy-out.

6. Using in Async Contexts

inout does not work across async boundaries.

7. Overusing inout

Too much inout leads to:

  • unpredictable code
  • hidden side effects
  • poor readability

If everything is mutable, you’re fighting Swift.


Best Practices (Opinionated)

  • Prefer immutability by default
  • Use inout only when mutation is the core intent
  • Name functions to reflect mutation clearly
  • Keep functions small and predictable
  • Never use inout just to “save a line”

If your API becomes harder to reason about, inout is the wrong choice.


Final Thoughts

inout is deceptively simple but it carries real design weight.

It forces you to decide:

  • Do I want a pure transformation?
  • Or explicit mutation?

Swift gives you both tools. Your job is to choose wisely.

Used well, inout makes your code:

  • more expressive
  • more intentional
  • and sometimes cleaner

Used poorly, it introduces:

  • hidden side effects
  • harder to follow logic
  • brittle APIs

So don’t avoid it but don’t default to it either.

Use inout when it earns its place.

If you have suggestions, feel free to connect with me on X and send me a DM. If this article helped you, Buy me a coffee.