How do list.copy(), lst[:] and copy.deepcopy() differ when copying a Python list?
answer
- Three different things get called copying
- The container is new, the elements are not
- What stops a self-referential structure recursing
- Immutable elements need no depth at all
basics
~20 slist.copy() and lst[:] are identical shallow copies: a new list holding the same element references, O(n) in pointers. copy.deepcopy() recursively copies the elements as well, so nested objects are independent — correct where a shallow copy is not, but far more expensive.
solid answer
~50 sPlain assignment `b = a` copies nothing — it binds a second name to the same list object. `a.copy()`, `a[:]`, `list(a)` and `copy.copy(a)` all produce the same thing: a **new** list holding references to the *same* elements. That is a shallow copy: appending to the copy no longer affects the original, but mutating an element reached through the copy still shows up in both. `copy.deepcopy(a)` recurses, building new objects for the nested contents too, so the two structures are fully independent. Deep copy keeps a memo table keyed by object identity, so cycles terminate and an object appearing twice stays shared in the clone. Cost is the deciding factor: shallow is a pointer copy, deep walks the whole object graph and can fail outright on objects tied to OS resources. Where only one nesting level matters, a comprehension such as `[dict(rec) for rec in records]` is the cheap middle ground.
code
pycon · 10 lines>>> import copy
>>> a = [[1, 2], [3, 4]]
>>> shallow = a.copy()
>>> deep = copy.deepcopy(a)
>>> shallow is a
False
>>> shallow[0] is a[0]
True
>>> deep[0] is a[0]
Falsego deeper
Be able to say that assignment copies nothing, and that a[:] or a.copy() gives you a list you can append to without touching the original. Know the phrase shallow copy and what it does not cover.
Explain the mechanics: a new list of the same element references, why c[0] is a[0] stays True, and what copy.deepcopy() adds by walking the object graph.
Show cost judgement: deep copy is proportional to the whole reachable graph and fails on objects holding OS handles, so prefer a one-level comprehension or rebuilding the structure where that suffices.
Own the design angle: prefer immutable data so copying is unnecessary, treat blanket deepcopy in a hot path as an unbounded cost accepted by accident, and decide where ownership of mutable state sits instead of defending it with copies.
### First: assignment is not a copy `b = a` creates no object. It binds another name to the list `a` already refers to, so `b is a` is `True` and `b.append(1)` is visible through `a`. Every discussion of copying starts here, because the bug being fixed is usually “I thought I had my own list”. ### The shallow copies, and why there are four spellings Four expressions produce an equivalent result: ```python import copy a = [[1, 2], [3, 4]] c1 = a.copy() c2 = a[:] c3 = list(a) c4 = copy.copy(a) ``` Each allocates a new list of the same length and stores the same element references into it, incrementing each element's reference count. Afterwards `c1 is not a` but `c1[0] is a[0]`. Structural mutation — `append`, `remove`, sorting the copy — affects only the copy. Mutating an element *through* the copy, such as `c1[0].append(9)`, is visible in the original, because both lists point at that one inner object. The spellings differ only in style and generality. `list(a)` accepts any iterable, which makes it the right choice when the source may be a generator or a tuple. `a.copy()` reads most clearly when the source is definitely a list and appeared in 3.3. `a[:]` is the oldest idiom and the one newcomers misread as “copying the data”. `copy.copy(a)` is the generic, type-agnostic form, useful when the object type is unknown. Cost is O(n) pointer copies — cheap and predictable, whatever the elements are, because nothing inside them is inspected. ### Deep copy `copy.deepcopy(a)` recursively copies the elements, and their elements, all the way down. Two mechanisms make that safe: **A memo table.** `deepcopy` keeps a dictionary keyed by the identity of everything it has already copied. That means a structure containing a reference to itself terminates instead of recursing forever, and — just as importantly — an object referenced from two places in the source is copied *once* and referenced from both places in the clone, preserving the sharing topology rather than silently duplicating. **Per-type hooks.** A class can control how it is deep-copied; where it does not, `deepcopy` falls back to a reconstruction protocol shared with pickling. That is also where deep copy fails: an object that cannot be pickled — a lock, a socket, an open file, a live database handle — raises `TypeError` from `deepcopy`. Discovering that in a request path is a common production surprise. Cost is proportional to the whole reachable object graph, not to `len(a)`, plus dictionary bookkeeping per object. On a large nested structure it is orders of magnitude slower than the shallow forms. ### Choosing Ask one question: *will anything mutate an object reached through the copy?* - If the elements are immutable — ints, strings, tuples of immutables — a shallow copy is already a complete copy, and deep copy buys nothing but cost. - If only the top level will be restructured — appending, filtering, reordering — shallow is correct. - If nested mutables will be modified independently, you need depth. Often you need exactly *one* level of it, and a comprehension gives that far more cheaply and far more legibly than a full deep copy: `[dict(rec) for rec in records]` or `[inner.copy() for inner in rows]`. - Reach for `copy.deepcopy` when the structure is arbitrarily nested, heterogeneous, or not under your control — and treat it as a cost you have accepted deliberately. A fourth option is worth naming: build the new structure rather than copying one. Where the data came from a serialized form, re-parsing it is sometimes both faster and clearer than deep-copying an in-memory graph, and it sidesteps the unpicklable-object problem entirely. ### What interviewers look for That you distinguish three things — binding a name, copying the container, copying the contents — and that you do not reach for `deepcopy` by reflex. “Use deepcopy to be safe” is the answer that marks a candidate down, because it converts a correctness question into an unbounded cost.
- Is `list(a)` any different from `a.copy()`?For a list source they produce the same shallow copy. The difference is generality: `list(...)` accepts any iterable, so it also works on a tuple, a set or a generator, while `.copy()` is a method that the source must actually have. Style guides usually prefer `a.copy()` when the source is known to be a list, because it names the intent.
- When is a shallow copy of a list already good enough?Whenever nothing will mutate an object reached through the copy. If the elements are immutable — numbers, strings, tuples of immutables — a shallow copy is indistinguishable from a deep one. It is also enough when only the container is restructured: appending, filtering, or sorting the copy leaves the original list untouched.
- What does copy.deepcopy() do when the same object appears twice in the structure?It copies it once. `deepcopy` maintains a memo table keyed by object identity, so the second encounter reuses the existing copy. That preserves the sharing topology of the original rather than duplicating the object, and it is the same mechanism that lets a structure containing a reference to itself be copied without infinite recursion.
- Are there objects copy.deepcopy() cannot handle?Yes. Where a class provides no copy hook, `deepcopy` falls back on the same reconstruction protocol pickling uses, so objects tied to OS or runtime resources — a lock, a socket, an open file, a live connection — raise `TypeError`. That is a good reason not to deep-copy objects that mix data with handles; copy the data and rebuild the handles.
A shallow copy is a new folder of shortcuts to the same files; a deep copy duplicates every file the shortcuts point at.
saying these in an interview costs you the question
- Says `b = a` copies the list
- Believes lst[:] copies the elements themselves
- Reaches for copy.deepcopy() by default to be safe
- Thinks deepcopy on a self-referencing structure recurses forever
- Assumes deepcopy works on every object, locks and sockets included