ASAmol Shukla
Projects
Courses
Prompts
Skills
Contact
Resume
Course Outline
Syllabus Overview

Complete Python Course: From Zero to Professional

Courses/Complete Python Course: From Zero to Professional/Lesson 11: Nested Structures, Aliasing & Copies
40 mins lesson duration•8 mins read

Lesson 11: Nested Structures, Aliasing & Copies

Build nested dicts and lists, understand how references behave, and copy safely.

Nested Structures — Data Inside Data

Real-world data is rarely flat. A student dict contains a list of scores. A company is a list of employee dicts. Python handles any nesting depth — you just need to keep the brackets straight:

employees = [
    {"name": "Amol", "skills": ["python", "ai"]},
    {"name": "Riya", "skills": ["sql", "pandas"]},
]
print(employees[0]["name"])        # Amol
print(employees[0]["skills"][0])   # python

Read nested access outside-in: the outermost bracket first, then work inward.

What You'll Learn in This Lesson

  • Build and navigate nested lists and dicts
  • Understand aliasing — two names, one object
  • Copy safely with shallow vs deep copies
  • Distinguish == (value) from is (identity)

Aliasing — Two Names, One Object

This is the trap that confuses every beginner:

original = [1, 2, [3, 4]]
alias = original          # NOT a copy!
alias.append(99)
print(original)           # [1, 2, [3, 4], 99] — original changed too!

When you write b = a, both names point to the SAME list in memory. Change one, and the other changes too. There is only one object — just two labels for it.

Draw arrows: alias and original are two arrows pointing at the same box. There is no second box.

Remember Lesson 2's mental model: = moves a name-tag to an object — it never copies the object. Aliasing is that rule biting back: two tags on one object means one change, two visible effects.


Shallow vs Deep Copy

To get an independent copy, you must ask for one — and there are two depths:

Copy How Nested objects?
Shallow a.copy(), list(a), a[:] Still shared
Deep copy.deepcopy(a) Fully independent
import copy

original = [1, 2, [3, 4]]

shallow = original.copy()
shallow[2].append(100)
print(original)      # [1, 2, [3, 4], 100] — inner list STILL shared!

deep = copy.deepcopy(original)
deep[2].append(200)
print(original)      # [1, 2, [3, 4], 100] — deep copy is fully independent
  • A shallow copy creates a new outer container, but the inner objects are still the same objects.
  • A deep copy duplicates everything, recursively — nothing is shared.

Mental model: shallow copy = photocopying the outer box but keeping the same inner boxes inside. Deep copy = rebuilding every single box from scratch.

When is shallow copy safe? When your structure is flat — no nested mutable objects. data = [1, 2, 3] copied shallowly is fully independent, because integers are immutable. The problem only appears with nested lists/dicts.


== vs is vs id

Three different questions:

Operator Asks Example
== Do they have the same value? [1,2] == [1,2] → True
is Are they the same object in memory? [1,2] is [1,2] → False
id(x) What is the object's memory address? debugging tool
a = [1, 2]
b = a          # same object
c = [1, 2]     # different object, same value
print(a == c)  # True  — same values
print(a is c)  # False — different objects
print(a is b)  # True  — same object

Use == for comparisons in normal code. Use is for singletons (is None, is True) and identity checks.

Professional rule: always compare to None with is — if x is None:, never if x == None:. It is faster, unambiguous, and the universal convention.


The [[0] * 3] * 3 Trap

Creating a matrix the "obvious" way is a classic bug:

bad = [[0] * 3] * 3      # 3 references to the SAME inner list!
bad[0][0] = 1
print(bad)               # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]  — oops!

The * operator repeats references, not copies. The correct way:

good = [[0] * 3 for _ in range(3)]   # 3 independent lists
good[0][0] = 1
print(good)              # [[1, 0, 0], [0, 0, 0], [0, 0, 0]]  — correct!

Why does * on a string or number work fine but fail on a list? Integers and strings are immutable, so sharing is harmless — no one can change a shared 0. Lists are mutable, so every "row" points at the same list you can mutate. The comprehension builds fresh lists each iteration.


Real-World Debugging Flow

When a program "mysteriously" mutates data:

  1. Ask: did I copy, or did I alias? Search for bare b = a.
  2. Ask: shallow or deep? Nested structures need deepcopy.
  3. Verify: print(id(a), id(b)) — identical ids mean one object.
  4. Fix: use .copy(), [:], or copy.deepcopy() at the assignment site.

Common Mistakes to Avoid

  • Mistake: b = a then mutating b and seeing a change — Fix: use a[:], .copy(), or copy.deepcopy().
  • Mistake: Shallow copy on nested data and still seeing shared inner lists — Fix: use copy.deepcopy() for nested structures.
  • Mistake: [[0]*3]*3 matrices sharing rows — Fix: build with a comprehension.
  • Mistake: x == None instead of x is None — Fix: use is for singletons.
  • Mistake: Using is to compare strings/numbers — Fix: is is for identity; use == for values.

Professional Tips & Tricks

  • If you need an independent list, always use .copy() or slicing — never bare assignment.
  • Use deepcopy only when structures are nested; it is slower.
  • Prefer immutable tuples for data you never change — they cannot be aliased-mutated.
  • Compare None with is, never ==.
  • For nested access, read outside-in and keep the brackets straight.

Key Takeaways

  • Nested access reads outside-in; mind the brackets.
  • Assignment (b = a) creates an alias, not a copy.
  • Shallow copies share inner objects; deep copies share nothing.
  • == compares values; is compares identity.
  • Build matrices with comprehensions, never [[0]*3]*3.
  • Use is None — never == None.

Next up: Module 4 — functions, your reusable building blocks.

Interactive Lesson Code Snippet
import copy

# Nested structure: list of dicts
employees = [
    {"name": "Amol", "skills": ["python", "ai"]},
    {"name": "Riya", "skills": ["sql", "pandas"]},
]
print("First employee:", employees[0]["name"])
print("Skills:", employees[0]["skills"])

# Aliasing: b refers to the SAME list
original = [1, 2, [3, 4]]
alias = original
alias.append(99)
print("Original mutated by alias:", original)

# Shallow copy: outer is new, inner list still shared
shallow = original.copy()
shallow[2].append(100)
print("Shallow copy affects original inner:", original)

# Deep copy: fully independent
deep = copy.deepcopy(original)
deep[2].append(200)
print("Deep copy leaves original untouched:", original)
Language: python

Lesson Code (Python)

import copy

# Nested structure: list of dicts
employees = [
    {"name": "Amol", "skills": ["python", "ai"]},
    {"name": "Riya", "skills": ["sql", "pandas"]},
]
print("First employee:", employees[0]["name"])
print("Skills:", employees[0]["skills"])

# Aliasing: b refers to the SAME list
original = [1, 2, [3, 4]]
alias = original
alias.append(99)
print("Original mutated by alias:", original)

# Shallow copy: outer is new, inner list still shared
shallow = original.copy()
shallow[2].append(100)
print("Shallow copy affects original inner:", original)

# Deep copy: fully independent
deep = copy.deepcopy(original)
deep[2].append(200)
print("Deep copy leaves original untouched:", original)

Console Output

First employee: Amol
Skills: ['python', 'ai']
Original mutated by alias: [1, 2, [3, 4], 99]
Shallow copy affects original inner: [1, 2, [3, 4], 99, 100]
Deep copy leaves original untouched: [1, 2, [3, 4], 99, 100]

Code Visualization Tips

  • 🧠Draw arrows: alias = second arrow to the same box; copy = a brand-new box.
  • 🧠Check id(original) vs id(alias) — identical ids mean one object.
  • 🧠For nested data, picture boxes inside boxes: shallow copy reuses the inner boxes, deep copy rebuilds them all.

Professional Tips & Tricks

  • ⚡If you need an independent list, always use .copy() or slicing — never bare assignment.
  • ⚡Use deepcopy only when structures are nested; it is slower.
  • ⚡prefer immutable tuples for data you never change — they cannot be aliased-mutated.

Python Code Judge & Practice Arena

LeetCode Style

Run real Python 3.12 WebAssembly code directly in your browser against automated test suites.

Solved:0 / 20
0 / 370 XP
Challenges:
Problem 1 of 20

Problem 1: Deep Copy Isolator 1

Easy+10 XP
Write a function `safe_deep_clone_1(nested_data, append_val)` that deep-clones nested_data using copy.deepcopy, appends append_val to the first list, and returns a tuple (original, cloned).
Sample Test Cases:
Input: safe_deep_clone_1([[1, 2], [3]], 99)
Expected: ([[1, 2], [3]], [[1, 2, 99], [3]])
Input: safe_deep_clone_1([[10]], 20)
Expected: ([[10]], [[10, 20]])
main.pyPython 3.12 (WASM)
1
2
3
4
5
6
7
8
9
10
11
12
Press Run Code to test or Submit to verify test cases

Test Your Knowledge

Instant feedback

Quick Check: Lesson 11: Nested Data Structures & Shallow vs Deep Copy

1 / 20
What is the output of this shallow copy modification? import copy a = [[1, 2], [3, 4]] b = copy.copy(a) b[0].append(99) print(a[0])

Up next · Continue learning

Functions — Reusable Building Blocks

Define functions, parameters, default arguments, return values, and docstrings.

9 mins read45 mins
Start next lesson
Previous: Advanced Collections — Counter, defaultdict & dequeNext: Functions — Reusable Building Blocks
Made withbyAmol Shukla·amolshukla.online
ASAmol Shukla

AI Developer, Trainer & Agentic AI Expert building practical learning systems and real-world AI applications.

Explore

  • Projects
  • Courses
  • Prompts
  • Skills
  • Contact
  • Experience
  • Blogs

Connect

  • Resume
  • Contact
© 2026 Amol Shukla·Created withbyamolshukla.online
Back to top