Swift
pass by reference
programming
arrays
software development

Swift Pass array by reference?

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

Swift arrays are value types, not reference types, so they are not "passed by reference" in the same sense as a class instance. However, Swift arrays use copy-on-write, which means passing an array to a function usually does not immediately copy the underlying storage. That is why arrays can feel reference-like in performance while still behaving as values semantically.

Arrays Are Value Types

An array in Swift is a struct. If you assign it to another variable, you conceptually get a new value:

swift
1var first = [1, 2, 3]
2var second = first
3
4second.append(4)
5
6print(first)   // [1, 2, 3]
7print(second)  // [1, 2, 3, 4]

From the programmer’s perspective, first and second are independent values. Mutating one does not change the other.

Why It Still Feels Efficient

Swift uses copy-on-write behind the scenes. When you pass or assign an array, Swift usually shares the underlying storage until one copy is mutated.

That means this function call does not necessarily create an eager full copy:

swift
1func printCount(_ numbers: [Int]) {
2    print(numbers.count)
3}
4
5let values = [1, 2, 3, 4, 5]
6printCount(values)

The array behaves like a value, but Swift avoids unnecessary copying until mutation forces separation.

This is the key idea: value semantics and efficient storage sharing can coexist.

Use inout to Modify the Caller’s Variable

If you want a function to update the caller’s array binding directly, use inout:

swift
1func appendValue(_ value: Int, to numbers: inout [Int]) {
2    numbers.append(value)
3}
4
5var values = [1, 2, 3]
6appendValue(4, to: &values)
7
8print(values) // [1, 2, 3, 4]

This is the closest Swift has to "pass by reference" for value types. The function still works within Swift’s safety rules, but the caller explicitly allows mutation of its variable.

Passing Normally Versus Passing with inout

These two functions behave differently:

swift
1func addNormally(_ numbers: [Int]) {
2    var localCopy = numbers
3    localCopy.append(99)
4    print(localCopy)
5}
6
7func addInPlace(_ numbers: inout [Int]) {
8    numbers.append(99)
9}
10
11var data = [1, 2, 3]
12
13addNormally(data)
14print(data) // [1, 2, 3]
15
16addInPlace(&data)
17print(data) // [1, 2, 3, 99]

Regular parameter passing preserves value semantics. inout lets the function mutate the caller’s variable.

When You Truly Need Shared Mutable State

If multiple parts of the program must observe and mutate the same array-like state, the cleaner answer is often not forcing "reference semantics" onto arrays. Instead, wrap the array in a class:

swift
1final class SharedNumbers {
2    var values: [Int]
3
4    init(values: [Int]) {
5        self.values = values
6    }
7}
8
9let shared = SharedNumbers(values: [1, 2, 3])
10let alias = shared
11
12alias.values.append(4)
13print(shared.values) // [1, 2, 3, 4]

Now the shared behavior is explicit because the class itself has reference semantics.

Common Pitfalls

One common mistake is assuming arrays are reference types just because passing them around seems cheap. Copy-on-write makes them efficient, but their semantics are still value-based.

Another mistake is expecting a normal function parameter to mutate the caller’s array. That requires inout or a reference-type wrapper.

Developers also sometimes overuse classes just to share array storage. If value semantics are actually what you want, plain arrays are usually the better design.

Finally, performance assumptions should be measured. Copy-on-write is efficient, but repeated mutations across many shared copies can still create real copy costs.

Summary

  • Swift arrays are value types with copy-on-write optimization.
  • Passing an array normally does not mean it is passed by reference semantically.
  • Use inout when a function should mutate the caller’s array variable.
  • Use a class wrapper only when shared mutable state is truly the goal.
  • Swift arrays are efficient precisely because they combine value semantics with delayed copying.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.