Swift Language12 min readJul 1, 2026

Mastering Copy-on-Write in Swift for Efficient Performance

Swift's Copy-on-Write (CoW) is a fundamental optimization technique that allows value types to behave like reference types until they are mutated. Understanding and utilizing CoW is crucial for writing performant and memory-efficient Swift applications, especially when dealing with collections and large data structures. This article dives deep into the mechanics and practical applications of CoW.

What is Copy-on-Write (CoW)?

Copy-on-Write (CoW) is an optimization strategy used extensively in Swift to enhance the performance of value types, particularly collections like Array, Dictionary, and Set, and even String. When you assign a value type instance (like an array) to a new variable or pass it as an argument to a function, Swift doesn't immediately create a deep copy of the underlying data. Instead, both variables or parameters refer to the same underlying storage. The actual copy only occurs when one of the instances is mutated. This deferred copying saves computational cost and memory allocations when multiple copies of a value type are made but not all of them are modified.

Think of it like this: if you have a blueprint for a house (the value type), and you give copies of that blueprint to several builders, they all share the same physical blueprint. Only when one builder decides to make a change (mutate) to their copy of the blueprint do they actually get a fresh, separate copy to modify. As long as they're just reading from it, they're all using the original. This intelligent optimization helps prevent unnecessary data duplication and memory churn, which are significant concerns in high-performance applications.

CoW in Swift's Standard Library Collections

Swift's standard library leverages Copy-on-Write heavily for its core collection types: Array, Dictionary, Set, and String. This is a significant performance feature. When you assign one array to another, or pass it to a function, the underlying elements are not immediately copied. This makes these operations seemingly O(1) (constant time) because only a reference to the buffer is copied. The actual O(n) (linear time) copy only happens if you modify the new instance. This behavior is transparent to the developer, meaning you don't need to explicitly manage the copying process; Swift handles it for you.

Let's look at an example with Array to illustrate this:

swift
var array1 = [1, 2, 3, 4, 5] // Original array
var array2 = array1        // array2 now points to the same underlying buffer as array1

print("array1: \(array1), array2: \(array2)") // Output: array1: [1, 2, 3, 4, 5], array2: [1, 2, 3, 4, 5]

// No copy has happened yet. Both variables share the same storage.

array2.append(6)           // Mutation occurs! A copy of array1's buffer is now made for array2.
                           // array1 still refers to the original buffer.

print("array1 after mutation: \(array1)") // Output: array1 after mutation: [1, 2, 3, 4, 5]
print("array2 after mutation: \(array2)") // Output: array2 after mutation: [1, 2, 3, 4, 5, 6]

In this example, initially array1 and array2 share the exact same memory. Only when array2.append(6) is called does Swift perform a copy operation, creating a new buffer for array2 and leaving array1 untouched. This behavior applies to other mutating operations like remove(at:), insert(at:), or assigning to an element via subscript.

swift
var originalNumbers = ["one", "two", "three"]
var copiedNumbers = originalNumbers // No deep copy yet, they share storage

print("Original: \(originalNumbers)")
print("Copied:   \(copiedNumbers)")

// Assert that they are currently sharing the same storage (conceptual check)
// In production, you wouldn't directly check memory addresses in Swift due to optimizations.

copiedNumbers.append("four") // Mutation occurs! A copy is made here.

print("\nOriginal after mutation: \(originalNumbers)") // Original is unchanged
print("Copied after mutation:   \(copiedNumbers)")   // Copied has the new element

// Expected Output:
// Original: ["one", "two", "three"]
// Copied:   ["one", "two", "three"]
//
// Original after mutation: ["one", "two", "three"]
// Copied after mutation:   ["one", "two", "three", "four"]

Implementing Custom Copy-on-Write for Your Types

While Swift's standard library collections provide CoW out of the box, you might encounter scenarios where you have a custom value type (a struct) that wraps a reference type (like a class instance, or a C pointer) and you want to implement CoW behavior for efficiency. A common pattern for this is to wrap the reference type in a helper class that internally tracks the number of references (like an NSBuffer or a custom Box type) and performs a deep copy only when a mutation is requested and there are multiple owners.

Let's define a simple Box class that holds a value and can track its uniqueness. Then, we'll embed this Box in a struct to create a CoW behavior for our custom type.

swift
// 1. Define a private Box class to hold the reference type and track uniqueness.
//    This box itself is a reference type. Mutating its 'value' will trigger a copy.
private class Box<T> {
    var value: T

    init(_ value: T) {
        self.value = value
    }
}

// 2. Define your value type (struct) that wraps the Box.
struct MyCoWStruct<T> {
    private var box: Box<T>

    init(_ value: T) {
        self.box = Box(value)
    }

    var value: T {
        get { return box.value }
        set {
            // Perform copy-on-write logic
            // Check if the current box is uniquely referenced.
            // if !isKnownUniquelyReferenced(&box) {
            //    box = Box(box.value) // Create a new box with a deep copy of the value
            // }
            // As of Swift 5.0, for classes, isKnownUniquelyReferenced can be used.
            // For structs containing class instances, this is critical.
            
            // A simpler approach for general-purpose value types within a Box is to 
            // always copy the box if it's not unique.
            // For custom COW, `isKnownUniquelyReferenced(&box)` is the Swift-idiomatic way.
            // Here, we'll use a direct check for the box's uniqueness (if possible)
            // or assume we need a copy if it's not truly unique.
            
            // Important: `isKnownUniquelyReferenced` works on class instances.
            // If `box` itself is being referred to by other `MyCoWStruct` instances,
            // this is where the magic happens.
            if !isKnownUniquelyReferenced(&box) {
                print("\n--- Performing Copy-on-Write ---")
                box = Box(box.value) // Create a new, unique box with the copied value
            }
            box.value = newValue // Now safely mutate the unique box's value
            print("Value mutated: \(newValue)")
        }
    }
}

// Example Usage:
var cowInstance1 = MyCoWStruct(Array(1...5)) // Holds an array (value type)
var cowInstance2 = cowInstance1              // Shares the same Box instance

print("Initial cowInstance1: \(cowInstance1.value)")
print("Initial cowInstance2: \(cowInstance2.value)")

cowInstance2.value.append(6)                 // Trigger CoW if not uniquely referenced

print("cowInstance1 after mutation: \(cowInstance1.value)") // Unchanged
print("cowInstance2 after mutation: \(cowInstance2.value)") // Changed

// Expected Output:
// Initial cowInstance1: [1, 2, 3, 4, 5]
// Initial cowInstance2: [1, 2, 3, 4, 5]
// 
// --- Performing Copy-on-Write ---
// Value mutated: [1, 2, 3, 4, 5, 6]
// cowInstance1 after mutation: [1, 2, 3, 4, 5]
// cowInstance2 after mutation: [1, 2, 3, 4, 5, 6]

Compatibility Note: The isKnownUniquelyReferenced(_:) function is available in Swift 3.0+ and compatible with iOS 8.0+, macOS 10.10+, watchOS 2.0+, tvOS 9.0+.

This pattern is powerful for when your struct needs to wrap a large or expensive-to-copy reference type and you want to maintain value semantics while benefiting from CoW optimization. Carefully consider if your internal reference type needs a deep copy or a shallow copy when designing your Box's initializer that creates the copy.

swift
private class ReferenceWrapper<T: NSCopying> {
    var value: T

    init(_ value: T) {
        self.value = value
    }

    func copy() -> ReferenceWrapper<T> {
        // Deep copy the underlying value if T conforms to NSCopying
        return ReferenceWrapper(value.copy() as! T)
    }
}

struct MyCoWStructWithNSCopying<T: NSCopying> {
    private var wrapper: ReferenceWrapper<T>

    init(_ value: T) {
        self.wrapper = ReferenceWrapper(value)
    }

    var value: T {
        get { wrapper.value }
        set {
            if !isKnownUniquelyReferenced(&wrapper) {
                print("\n*** Performing Copy-on-Write for NSCopying type ***")
                wrapper = wrapper.copy() // Create a new wrapper with a deep-copied value
            }
            wrapper.value = newValue // Now, safely mutate the unique wrapper's value
            print("Value in COW struct mutated to: \(newValue)")
        }
    }
}

// Example with an NSCopying-conforming type like NSMutableString
let originalMutableString = NSMutableString(string: "Hello")
var cowString1 = MyCoWStructWithNSCopying(originalMutableString)
var cowString2 = cowString1 // Shares internal wrapper

print("\nInitial cowString1: \(cowString1.value)")
print("Initial cowString2: \(cowString2.value)")

cowString2.value.append(", World!") // Will trigger CoW via the 'set' for value

print("cowString1 after mutation: \(cowString1.value)") // Unchanged original
print("cowString2 after mutation: \(cowString2.value)") // Changed copy

// Expected Output:
// Initial cowString1: Hello
// Initial cowString2: Hello
// 
// *** Performing Copy-on-Write for NSCopying type ***
// Value in COW struct mutated to: Hello, World!
// cowString1 after mutation: Hello
// cowString2 after mutation: Hello, World!

Benefits and Considerations of CoW

Benefits

  1. Performance Optimization: CoW significantly reduces the number of unnecessary memory allocations and data copying operations. This is especially impactful for large collections or data structures that are frequently passed around but rarely modified. By deferring the copy, Swift avoids expensive operations until absolutely necessary.
  2. Memory Efficiency: By sharing underlying storage, CoW minimizes memory footprint. Multiple variables can point to the same data, leading to more efficient use of RAM.
  3. Value Semantics Preservation: CoW allows value types to maintain their predictable value semantics (changes to one copy don't affect others) while gaining the performance benefits often associated with reference types for read-only operations. This simplifies reasoning about code and prevents unexpected side effects.

Considerations

  1. Overhead of isKnownUniquelyReferenced: While highly optimized, calling isKnownUniquelyReferenced(_:) does carry a small performance cost. For very small types or situations where mutation is extremely frequent, the overhead of checking uniqueness and potentially copying might outweigh the benefits. In such cases, a direct copy might be simpler and potentially faster.
  2. Debugging Complexity: When dealing with custom CoW implementations, understanding exactly when a copy happens can sometimes be tricky during debugging. Uniquely referenced checks happen behind the scenes, and you might not always be aware if two variables are sharing storage or have separate copies.
  3. Deep vs. Shallow Copies: When implementing your own CoW structs that wrap reference types, you must carefully consider whether your Box's init or copy method performs a deep copy (recursively copying all contents) or a shallow copy (copying only the top-level references). If your boxed reference type contains other reference types, a shallow copy might still lead to unintended shared state. For ultimate value semantics, a deep copy is often required, which can be computationally expensive.

When to Implement Custom CoW?

You should consider implementing a custom Copy-on-Write mechanism for your structs in Swift in the following scenarios:

  • Your struct wraps a large or expensive-to-copy class instance: If your value type inherently contains a reference type that is substantial in size or creation cost (e.g., a custom buffer, an image data object, a large graph structure).
  • Your struct is frequently copied but rarely mutated: If instances of your struct are often passed as function arguments, returned from functions, or assigned to new variables, but only a small fraction of these copies are ever modified.
  • You want to maintain strict value semantics over a reference type: You desire your wrapper struct to behave exactly like a value type, where modifying one instance never affects another, even though its internal storage is a reference type.
  • Performance critical sections: In areas of your application where memory allocation and copying are identified as performance bottlenecks, and traditional value semantics (always copying) are proving too slow.

Avoid custom CoW for:

  • Small, trivial types: For structs composed solely of primitive types (Int, Bool, String) or other small value types, the overhead of CoW often outweighs the benefits. Swift is highly optimized for these default cases.
  • Types that are always modified after copying: If you know that every copy of your struct will be immediately mutated, there's no benefit to deferring the copy; a direct copy would be simpler and potentially faster.
  • When reference semantics are desired: If you explicitly want changes to one 'copy' to reflect in others, then CoW is counterproductive, and you should probably use a class directly.

Value types always make full copies

Becoming a stronger iOS Engineer

THE MYTH or PROBLEM: Value types always make full copies

Many developers assume that assigning a struct or passing it to a function always results in an immediate, full deep copy of its entire contents. While true for simple value types, this is false for Swift's collections and custom structs that implement Copy-on-Write, leading to potential performance bottlenecks if not understood.

swift
var originalLargeArray = Array(0..<1_000_000)
var copiedLargeArray = originalLargeArray // Myth: Immediate deep copy happens here.
copiedLargeArray[0] = 1 // Reality: Only now is a deep copy made for `copiedLargeArray`.

WHAT HAPPENS INTERNALLY? (Array CoW Example)

Swift's `Array` (and other CoW types) internally wraps a `_ContiguousArrayStorage` class instance. This class holds the actual elements and is reference counted. When you copy an `Array`, only the reference to this storage is copied, not the elements themselves.

Value Type (Struct)
Reference to Storage (Class)
1

1. Initial Assignment

var arr1 = [1,2,3]. `arr1` holds a struct that contains a reference to `Storage A`.

2

2. Shallow Copy

var arr2 = arr1. `arr2` holds a copy of the struct, now also referencing `Storage A`.

3

3. Uniqueness Check

arr2.append(4). Before mutation, Swift checks if `Storage A` is uniquely referenced (i.e., only `arr2` refers to it).

4

4. Copy-on-Write Trigger

If `Storage A` is *not* unique (i.e., `arr1` also refers to it), Swift creates `Storage B`, deep-copies elements from `Storage A` into `Storage B`.

5

5. Mutate Unique Storage

`arr2`'s internal struct now points to `Storage B`. The mutation (`append(4)`) is applied to `Storage B`. `arr1` remains linked to `Storage A`, which is unchanged.

Visualized execution hierarchy.

Powerful Guarantees

Value Semantics Maintained

Even with internal reference types, CoW ensures your struct behaves like a true value type; changes to one copy don't affect others.

Optimized Performance

Avoids expensive deep copies for read-only operations, significantly boosting performance for large data sets.

Memory Efficiency

Multiple value-type instances can share the same underlying memory buffer until a write operation occurs.

REAL PRODUCTION EXAMPLE: Immutable View States

Imagine a complex `SettingsState` struct that contains large `Array` and `Dictionary` properties. You pass this state to multiple SwiftUI views or use it across different features. If `SettingsState` always performed deep copies, UI updates or simple reads would be slow. Thanks to CoW in the underlying collections, the state is efficiently handled.

Impact / Results
Smoother UI updates (no lag from copying)
Lower memory footprint for shared states
Predictable state management
THE FIX or SOLUTION: Trust Swift's CoW
swift
struct SettingsState: Equatable, Codable {
    var userPreferences: [String: String] // Dictionary is CoW
    var recentActivityLogs: [String]      // Array is CoW
    var appConfiguration: Data?           // Data is CoW
}

var globalState1 = SettingsState(userPreferences: ["theme": "dark"], recentActivityLogs: ["Login"], appConfiguration: nil)
var globalState2 = globalState1 // No deep copy of preferences/logs here

// Reading doesn't trigger a copy
print("Current theme: \(globalState2.userPreferences["theme"] ?? "light")")

globalState2.userPreferences["theme"] = "light" // CoW triggered for userPreferences
globalState2.recentActivityLogs.append("Changed Theme") // CoW triggered for recentActivityLogs

print("\nGlobal State 1 (original): \(globalState1)") // Unchanged
print("Global State 2 (modified): \(globalState2)")   // Changed

INTERVIEW PERSPECTIVE

Common Question

Explain Swift's Copy-on-Write mechanism. When and why would you implement it for a custom type?

Strong Answer

CoW is an optimization where a value type's underlying data (often a reference type) is shared until a mutation occurs, at which point a copy is made. You'd implement it for custom structs that wrap large/expensive-to-copy reference types, are frequently copied but rarely mutated, and where strict value semantics are desired. This prevents unnecessary memory allocations and improves performance.

Interviewers Expect you to understand:
  • Definition of CoW
  • Examples (Arrays, Strings)
  • `isKnownUniquelyReferenced`
  • Use cases for custom CoW
  • Performance/memory benefits
KEY TAKEAWAY

Embrace Swift's Copy-on-Write. It's a powerful and transparent optimization for collections and a valuable pattern for your custom structs that wrap expensive reference types. Understand *when* copies occur to write performant and memory-efficient Swift code.

Frequently Asked Questions

What is the main driver behind Swift's use of Copy-on-Write for collections?
The main driver is performance and memory efficiency. By deferring the deep copy of data until a mutation occurs, Swift avoids unnecessary allocations and copying operations, especially when collections are passed around but not modified, making operations like assignment or function passing significantly faster (O(1) instead of O(N)).
Does Copy-on-Write apply to all Swift value types?
No. CoW is primarily an optimization for value types that internally manage reference-counted storage. This includes Swift's standard library `Array`, `Dictionary`, `Set`, and `String` types. Simple value types like `Int`, `Bool`, or structs composed entirely of other simple value types (without any internal reference types) are copied directly every time they are assigned or passed, as the cost of copying their raw bytes is negligible compared to the overhead of reference counting and unique reference checks.
How can I tell if my custom CoW implementation is working?
You can add `print` statements within your `newValue` setter for your boxed value, specifically inside the `if !isKnownUniquelyReferenced(&box)` block. This will clearly indicate when a copy-on-write operation is performed. You can also use profilers like Instruments (specifically, the Allocations instrument) to observe memory allocation patterns.
What is `isKnownUniquelyReferenced(_:)` used for?
`isKnownUniquelyReferenced(_:)` is a Swift standard library function that checks if a class instance (or a captured class instance if you're working with closures/generics) has a single strong reference to it. It's crucial for implementing custom Copy-on-Write behavior for value types that wrap reference types, allowing you to perform a copy only when multiple owners exist.
Is CoW always beneficial for performance?
Not always. While generally beneficial, CoW introduces a small overhead for checking uniqueness. For very small value types or scenarios where a copy is *always* followed by a mutation, the overhead of the `isKnownUniquelyReferenced` check might outweigh the benefit of avoiding a copy. In such niche cases, a direct copy can sometimes be more performant or simpler to reason about.
#Swift#Copy-on-Write#Performance#Memory Management#Value Types#Structs