by Payal Gupta
Deep copy vs. shallow copy — and how you can use them in Swift

Copying an object has always been an essential part in the coding paradigm. Be it in Swift, Objective-C, JAVA or any other language, we’ll always need to copy an object for use in different contexts.
In this article, we’ll discuss in detail how to copy different data types in Swift and how they behave in different circumstances.
Value and Reference types
All the data types in Swift broadly fall into two categories, namely value types and reference types.
- Value type — each instance keeps a unique copy of its data. Data types that fall into this category include —
all the basic data types, struct, enum, array, tuples
. - Reference type — instances share a single copy of the data, and the type is usually defined as a
class
.
The most distinguishing feature of both the types lies in their copying behaviour.
What is Deep and Shallow copy?
An instance, whether it’s a value type or a reference type, can be copied in one of the following ways:
Deep copy — Duplicates everything
- With a deep copy, any object pointed to by the source is copied and the copy is pointed to by the destination. So two completely separate objects will be created.
- Collections — A deep copy of a collection is two collections with all of the elements in the original collection duplicated.
- Less prone to race conditions and performs well in a multithreaded environment — changes in one object will have no effect on another object.
- Value types are copied deeply.
In the above code,
- Line 1:
arr1
— array (a value type) of Strings - Line 2:
arr1
is assigned toarr2
. This will create a deep copy ofarr1
and then assign that copy toarr2
- Lines 7 to 11: any changes done in
arr2
don’t reflect inarr1
.
This is what deep copy is — completely separate instances. The same concept works with all the value types.
In some scenarios, that is when a value type contains nested reference types, deep copy reveals a different kind of behaviour. We’ll see that in upcoming sections.
Shallow copy — Duplicates as little as possible
- With a shallow copy, any object pointed to by the source is also pointed to by the destination. So only one object will be created in the memory.
- Collections — A shallow copy of a collection is a copy of the collection structure, not the elements. With a shallow copy, two collections now share the individual elements.
- Faster — only the reference is copied.
- Copying reference types creates a shallow copy.
In the above code,
- Lines 1 to 8:
Address
class type - Line 10:
a1
— an instance ofAddress
type - Line 11:
a1
is assigned toa2
. This will create a shallow copy ofa1
and then assign that copy toa2
, that is only the reference is copied intoa2
. - Lines 16 to 19: any changes done in
a2
will certainly reflect ina1
.

In the above illustration, we can see that both a1
and a2
point to the same memory address.
Copying Reference Types Deeply
As of now, we know that whenever we try to copy a reference type, only the reference to the object is copied. No new object is created. What if we want to create a completely separate object?
We can create a deep copy of the reference type using the copy()
method. According to the documentation,
copy() — Returns the object returned by copy(with:)
.
This is a convenience method for classes that adopt the NSCopying
protocol. An exception is raised if there is no implementation for copy(with:)
.
Let’s restructure the Address class
we created in Code Snippet 2 to conform to the NSCopying
protocol.
In the above code,
- Lines 1 to 14:
Address
class type conforms toNSCopying
and implementscopy(with:)
method - Line 16:
a1
— an instance ofAddress
type - Line 17:
a1
is assigned toa2
usingcopy()
method. This will create a deep copy ofa1
and then assign that copy toa2
, that is a completely new object will be created. - Lines 22 to 25: any changes done in
a2
will not reflect ina1
.

As is evident from the above illustration, both a1
and a2
point to different memory locations.
Let’s look at another example. This time we’ll see how it works with nested reference types — a reference type containing another reference type.
In the above code,
- Line 22: a deep copy of
p1
is assigned top2
using thecopy()
method. This implies that any change in one of them must not have any effect on the other one. - Lines 27 to 28:
p2’s
name
andcity
values are changed. These must not reflect inp1
. - Line 30:
p1’s
name
is as expected, but itscity
? It should be“Mumbai”
shouldn’t it? But we can’t see that happening.“Bangalore”
was only forp2
right? Yup…exactly.?
Deep copy…!? That was not expected from you. You said you’ll copy everything. And now you are behaving like this. Why oh why..?! What do I do now? ☠️
Don’t panic. Let’s look at what memory addresses has to say in this.

From the above illustration, we can see that
p1
andp2
point to different memory locations as expected.- But their
address
variables are still pointing to the same location. This means that even after copying them deeply, only the references are copied — that is, a shallow copy of course.
Please note: every time we copy a reference type, a shallow copy is created by default until we explicitly specify that it should be copied deeply.
func copy(with zone: NSZone? = nil) -> Any{ let person = Person(self.name, self.address) return person}
In the above method we implemented earlier for the Person
class, we have created a new instance by copying the address with self.address
. This will only copy the reference to the address object. This is the reason why both p1
and p2’s
address
point to the same location.
So, copying the object using the copy()
method won’t create a true deep copy of the object.
To duplicate a reference object completely: the reference type along with all the nested reference types must be copied with the copy()
method.
let person = Person(self.name, self.address.copy() as? Address)
Using the above code in the func copy(with zone: NSZone? = nil) ->
Any method will get everything working. You can see that from the below illustration.

True Deep Copy — Reference and Value types
We’ve already seen how we can create a deep copy of the reference types. Of course we can do that with all the nested reference types.
But what about the nested reference type in a value type, that is an array of objects, or a reference type variable in a struct or maybe a tuple? Can we resolve that using copy()
too? No we can’t, actually. The copy()
method requires implementing NSCopying
protocol which only works for NSObject
subclasses. Value types don’t support inheritance, so we can’t use copy()
with them.
In line 2, only the structure of arr1
is deep copied, but the Address
objects inside it are still shallow copied. You can see that from the below memory map.

The elements in both arr1
and arr2
both point to the same memory locations. This is because of the same reason — reference types are shallow copied by default.
Serializing and then de-serializing an object always creates a brand new object. It is valid for both value types as well as the reference types.
Here are some APIs that we can use to serialize and de-serialize data:
- NSCoding — A protocol that enables an object to be encoded and decoded for archiving and distribution. It will only work with
class
type objects as it requires inheriting fromNSObject
. - Codable — Make your data types encodable and decodable for compatibility with external representations such as JSON. It will work for both value types —
struct, array, tuple, basic data types
well as reference types —class
.
Let’s restructure the Address
class a bit further to conform to the Codable
protocol and remove all the NSCopying
code that we added earlier in Code Snippet 3.
In the above code, lines 11–13 will create a true deep copy of arr1
. Below is the illustration that gives a clear picture of the memory locations.

Copy on Write
Copy on write is an optimization technique that helps boost performance when copying value types.
Let’s say we copy a single String or Int or maybe any other value type — we won’t face any crucial performance issues in that case. But what about when we copy an array of thousands of elements? Will it still not create any performance issues? What if we just copy it and don’t make any changes to that copy? Isn’t that extra memory we used just a waste in that case?
Here comes the concept of Copy in Write — when copying, each reference points to the same memory address. It’s only when one of the references modifies the underlying data that Swift actually copies the original instance and makes the modification.
That is, whether it’s deep copy or shallow copy, a new copy will not be created until we make a change in one of the objects.
In the above code,
- Line 2: a deep copy of
arr1
is assigned toarr2
- Lines 4 and 5:
arr1
andarr2
still point to the same memory address - Line 7: changes made in
arr2
- Lines 9 and 10:
arr1
andarr2
now pointing to different memory locations
Now you know more about deep and shallow copies and how they behave in different scenarios with different data types. You can try them with your own set of examples and see what results you get.
Further reading
Don’t forget to read my other articles:
- Everything about Codable in Swift 4
- Everything you’ve always wanted to know about notifications in iOS
- Color it with GRADIENTS — iOS
- Coding for iOS 11: How to drag & drop into collections & tables
- All you need to know about Today Extensions (Widget) in iOS 10
- UICollectionViewCell selection made easy..!!
Feel free to leave comments in case you have any questions.