Swift & Objective-C

Shallow vs. Deep Copy: Up to Swift COW

Reference, shallow, and deep copies differ in how much newly created data they duplicate. This article explains the pitfalls of reference types inside Swift value types and how Copy-on-Write preserves independent copies.

4 min read
Cover image for Shallow vs. Deep Copy: Up to Swift COW

I copied an array into a new variable. I changed only the copy, but the original changed too. It’s a programming mystery everyone encounters at least once.

The culprit is the two faces of “copy.” One copy duplicates the entire value; the other adds another label pointing to the same data.

We’ll clarify the distinction between shallow and deep copies, then see how Swift changes the situation. Value types and Copy-on-Write help, but pitfalls remain.

Here’s the key summary.

Applying this distinction to object-creation patterns leads to Swift Prototype Pattern and NSCopying, while the implementation that defers actual copying for performance is covered in detail in Swift Copy-on-Write.

  1. Reference copy: only the label pointing to the data is duplicated. Changing one side changes the other.
  2. Shallow copy: the outer shell is new, but the references inside still point to the same objects.
  3. Deep copy: everything inside is recursively recreated. The copies are completely independent.
  4. Swift value types behave like independent copies on assignment, but when they contain a class, the shallow-copy problem returns unchanged.

You need to distinguish three levels

People often describe only shallow and deep copies, but the picture is accurate only when reference copying is included as a third level.

Reference copying is not copying; it is assigning an alias. Assigning a class instance to another variable leaves one object with two pointers to it.

class Profile { var name = "Developer Kim" }
let a = Profile()
let b = a          // Reference copy: one object⟧
b.name = "Coder Lee"
print(a.name)      // "Coder Lee” — the original changes too

Shallow copying creates only one new outer layer. If the contents are references, the objects they point to remain shared.

Deep copying recreates the box, the boxes inside it, and everything beneath them. The two structures are fully independent, so changing either has no effect on the other.

A shallow copy recreates only the shell; a deep copy recreates everything inside
A shallow copy recreates only the shell; a deep copy recreates everything inside

Swift Value Types: Assignment Is Copying

Swift structs, enums, and primitive types are value types, so assigning them duplicates the value immediately.

var original = [1, 2, 3]
var copy = original
copy.append(4)
print(original) // [1, 2, 3] — Safe

That differs greatly from languages where assigning an array performs a reference copy. In Python, assigning a list makes both variables point to the same list.

“Wouldn’t copying everything every time be slow?” This is where Copy-on-Write (COW) comes in. Array, Dictionary, and String share their internal buffer at assignment, then perform the actual copy only when one side is modified. Reads incur no copying cost, while the values behave as independent values semantically. It combines value-type safety with the efficiency of shared references.


But pitfalls remain

As soon as a value type contains a reference type, the shallow-copy problem comes back.

class Attachment { var filename = "a.png" }

struct Mail {
    var title: String
    var attachment: Attachment  // A class inside a struct
}

var mail1 = Mail(title: "Original", attachment: Attachment())
var mail2 = mail1               // The struct is copied, but...
mail2.attachment.filename = "b.png"
print(mail1.attachment.filename) // "b.png" — The original changes too!

The Mail struct was clearly copied. title is independent. But attachment contained a “reference,” so the copied property is also a reference. Both Mail values now share the same Attachment. That is exactly the definition of a shallow copy.

To solve this, you must create a deep copy yourself. Write copying logic that creates a new Attachment, or change Attachment into a struct in the first place. In the Objective-C family, implementing NSCopying’s copy(with:) is the traditional approach; if a class contains another class, that nested object must also be copied recursively for a true deep copy.

The practical conclusion aligns with why Swift recommends value types. If a model consists only of structs and value-type properties, the problem disappears entirely.

Using only value types in a model eliminates the problem entirely
Using only value types in a model eliminates the problem entirely

In One Sentence for Interviews

“A shallow copy creates only a new top-level shell, leaving internal references shared; a deep copy recursively duplicates the internals and makes the copy fully independent. Swift value types behave like independent copies on assignment, but reference-type properties inside them are still shared, so be careful.”

For follow-up questions, it’s enough to explain “What is COW?” (an optimization that defers copying until modification) and “How do you implement a deep copy?” (NSCopying or manual recursive duplication).


Summary

  • Understand copying in three stages: reference copy → shallow copy → deep copy
  • Reference copy duplicates only the label (shared object); shallow copy duplicates only the shell (shared internal references); deep copy duplicates everything (fully independent).
  • For Swift value types, assignment is copying; Array and String defer actual copying until modification through COW.
  • A class property inside a value type reproduces the shallow-copy problem unchanged.
  • When a deep copy is required, implement NSCopying or manual copying logic recursively.
  • The fundamental solution is to build models from value types—another reason Swift recommends structs.