Shallow vs deep copy

RoadmapsPython Backend

Overview

Python variables are labels that point to objects (boxes). Assignment with `=` never copies an object — it attaches a second label to the same box. When you need a real copy, you choose between *shallow* and *deep*. A shallow copy creates a new outer container but keeps the same references to the inner objects. Every built-in shortcut — `list.copy()`, `dict.copy()`, `copy.copy()`, `list[:]`, and `list()` — does a shallow copy: original = [[1, 2], [3, 4]] import copy shallow = copy.copy(original) print(original is shallow) # False — new outer list print(original[0] is shallow[0]) # True — inner list still shared A deep copy, via `copy.deepcopy()`, recursively copies every nested object so nothing is shared: deep = copy.deepcopy(original) print(original[0] is deep[0]) # False — independent inner list Mental model: shallow copy makes a new box whose labels still point to the same inner boxes. Deep copy makes new boxes all the way down. Shallow copy is safe and sufficient when the collection is flat and contains only immutables (strings, ints, tuples of immutables). You need `deepcopy` when the structure contains nested mutable values — lists inside lists, dicts inside dicts — and you want to mutate one copy without affecting the other.

Real-world Python code constantly clones data — duplicating a config dict before overriding keys, snapshotting nested API payloads, or isolating state between test runs. Choosing the wrong copy level is a silent, hard-to-trace bug: you modify what you think is an independent copy and corrupt the original.

Where used: FastAPI, Pydantic, Django, pytest

Why learn this

Code walkthrough

import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)
original[0].append(99)
print(shallow[0])
print(deep[0])

Focus: Observe that `shallow[0]` gets modified along with `original`, whereas `deep` remains fully independent.

Common mistakes

Glossary

container
An object, like a list or dictionary, that holds or stores other objects inside it. Example: `my_list = [1, 2]`.
snapshotting
Creating an exact copy or record of the current state of data at a specific moment in time. Example: `backup = copy.deepcopy(data)`.

Recall questions

Understanding checks

What is the output of the following code?

[2, 3, 4]

`copy.copy()` creates a shallow copy, meaning the outer list is new but the inner list `[2, 3]` is still shared between `original` and `cloned`.

Which copying method would you use to duplicate a configuration dictionary that contains nested lists, so you can safely mutate the copy's lists without affecting the original?

`copy.deepcopy()`

Since the dictionary contains nested mutable objects (lists), a shallow copy (`dict.copy()`) would still share the inner lists. A deep copy recursively copies all nested objects.

Practice tasks

Deep-clone nested API payload

The `duplicate_order` function creates a shallow copy of the order. Modify it to create a fully independent copy using `copy.deepcopy()` so that appending to the copy's items list doesn't affect the original.

Challenge

Snapshot middleware for request context

Write a function `snapshot_context(ctx)` that receives a nested dict representing a request context (with keys like 'user', 'headers' as a dict, and 'query_params' as a list). It should return a frozen snapshot — a deep copy — so that downstream handlers can mutate their own copy while the original context remains pristine. Demonstrate by modifying the snapshot's 'query_params' and showing the original is unaffected.

Continue learning

Previous: Mutable vs immutable types

Previous: Variables as references

Previous: Slicing

Previous: dict internals & ordering

Return to Python Backend Roadmap