Hands-on copy.copy vs deepcopy vs manual shallow lab: real ops/s on nested dicts and lists of several sizes, measured on Linux localhost only (eng lab).
4 min read
copy.copy vs copy.deepcopy vs a one-liner dict(obj) / list(obj): folklore says deepcopy is “slow” without saying how slow as nesting grows. This lab times all three on flat dicts, nested dicts/lists, and lists of dicts on Linux localhost.
Related links:
Lab honesty (1 Oct 2026 IST): Python 3.13.5. Affiliates: 0. Manual arm is one-level (dict() / list()), not a hand-rolled recursive clone. No custom __deepcopy__.
Verdict up front: shallow copy stays in the millions of ops/s until the top container is large. deepcopy is the cliff: flat dict N=16 is already ~0.022× copy (175k vs 8.00M/s). List of 256 dicts: deepcopy ~1,588/s vs copy ~2.11M.
| Arm | What you get |
|---|---|
copy.copy | new top object; children shared |
dict(obj) / list(obj) | same shallow contract, often slightly faster |
copy.deepcopy | new tree; mutating a child does not touch the original |
If you only needed a new top dict so you can add a key, deepcopy is paying for isolation you did not use.
Script: lab-evidence/50-copy-vs-deepcopy/results/run_lab.py.
| Structure | copy.copy | deepcopy | manual shallow | deep÷copy |
|---|---|---|---|---|
| flat dict N=16 | 7,997,442 | 175,387 | 12,186,946 | 0.022× |
| flat dict N=128 | 1,976,167 | 23,675 | 2,170,633 | 0.012× |
| flat dict N=1024 | 253,991 | 3,130 | 261,194 | 0.012× |
| nested dict depth 2 w=4 | 10,136,334 | 121,633 | 16,656,955 | 0.012× |
| nested dict depth 4 w=4 | 9,920,146 | 49,613 | 15,481,073 | 0.005× |
| nested dict depth 6 w=4 | 10,072,012 | 31,229 | 15,984,647 | 0.003× |
| nested list depth 2 w=4 | 11,578,765 | 205,229 | 21,619,297 | 0.018× |
| nested list depth 4 w=4 | 11,682,247 | 89,442 | 21,083,701 | 0.008× |
| list of dicts N=32 | 8,463,102 | 12,510 | 11,671,335 | 0.001× |
| list of dicts N=256 | 2,112,289 | 1,588 | 1,775,726 | 0.001× |
Shallow copy of a nested dict stays ~10M/s at depth 6 because it does not walk children. Deepcopy falls from ~122k/s (depth 2) to ~31.2k/s (depth 6). That is the whole lesson: depth is free for copy and expensive for deepcopy.
dict() / list() beat copy.copy on these builtins (~1.52× on the small flat dict; ~1.87× on a small nested list). copy.copy pays a dispatch layer; the result is still shallow.None then a new dict inside) instead.dict(d) deep-copies values — it does not.__deepcopy__ can be faster or slower.| Need | Prefer |
|---|---|
| New top mapping, children stay shared | dict(obj) or copy.copy |
| Isolate nested mutations | deepcopy — measure if N is large |
| One extra key on a request dict | shallow merge, not deepcopy |
| Big payload clone per request | avoid; copy only the fields you change |
Evidence: /workspace/lab-evidence/50-copy-vs-deepcopy/results/.
Shallow is cheap; deep walks the tree. On this box a 16-key dict is ~8.00M copy.copy/s vs ~175k deepcopy. Depth 6 nested dict: copy stays ~10.1M/s while deepcopy drops to ~31k. A 256-row list of dicts deepcopy is ~1,588/s. Use deepcopy when isolation is the requirement — not as a default clone.
Lab evidence
Lab 1 Oct 2026 IST. Python 3.13.5. flat dict N=16: copy 8.00M/s; deepcopy 175k (0.022x); manual 12.19M. N=1024: copy 254k; deepcopy 3.13k (0.012x). nested dict d6: copy 10.1M vs deepcopy 31.2k. list-of-dicts n=256: deepcopy 1588/s vs copy 2.11M. Affiliates: 0. Evidence: lab-evidence/50-copy-vs-deepcopy/.
Hands-on dataclasses.replace vs manual copy/new-instance lab: real ops/s updating one field, measured on Linux localhost in this hands-on lab for SREs.
Summary1 Oct 20265 min read
Hands-on platform.platform vs os.uname host inventory lab: real ops/s plus cache notes, measured on Linux localhost today in this hands-on lab for SREs.
Summary1 Oct 20264 min read
Hands-on uuid.uuid4 vs uuid.uuid1 ID generation lab: real ops/s plus version/node checks, measured on Linux localhost today in this hands-on lab for SREs.
Summary1 Oct 20263 min read