Skip to content
Hello Python
6/8

Lists & Tuples

Topic 6 of 8, with 4 concept checks. Ordered collections, comprehensions, and copying

Choose mutability before choosing a sequence

Sequence choices

Compare lists and tuples by the changes an algorithm must make, then reason about indexing, unpacking, copying, aliasing, and iteration from that mutability decision.

Core lesson 01

Lists: mutable, []. Tuples: immutable, (), hashable if their contents are hashable.

Beyond the surface syntax, mutability is the real distinction. A list can have elements added, removed, or replaced without changing its identity. A tuple's contents are fixed at creation. Because tuples can't change, they can be hashed (as long as everything inside them is also hashable), which makes them usable as dict keys or set members — something lists can never do.

Python example
point = (3, 4)
# point[0] = 5   # TypeError: 'tuple' object does not support item assignment

lookup = {(0,0): "origin", (1,1): "diag"}   # tuple as dict key -- fine
# lookup[[0,0]] = "x"   # TypeError: unhashable type: 'list'

What to remember

What's the key structural difference between a list and a tuple?

Common footguns

  • Believing a tuple is deeply immutable — `t = ([1,2], 3)`; `t[0].append(9)` works fine, because the *tuple's slots* are fixed, not the mutability of what's inside them.

Core lesson 02

[0, 4, 16] — the if filters to even x first (0, 2, 4), then each is squared.

A list comprehension `[expr for item in iterable if cond]` reads like a for-loop written inline: iterate, filter with `if`, then apply `expr` to what survives, collecting results into a new list. This is Python's preferred, more readable, and often faster alternative to building a list with .append() in a loop.

Python example
result = [x**2 for x in range(5) if x % 2 == 0]
print(result)          # [0, 4, 16]

# equivalent manual loop:
result2 = []
for x in range(5):
    if x % 2 == 0:
        result2.append(x**2)
print(result2 == result)   # True

What to remember

What does [x**2 for x in range(5) if x % 2 == 0] produce?

Common footguns

  • Nesting multiple for/if clauses in one comprehension until it's harder to read than the loop it replaced — split it up or use a generator function instead.

Core lesson 03

append(x) adds x as ONE new element (even if x is a list). extend(iterable) adds each of its elements individually.

Both mutate the list in place and both accept any object, but they treat that object differently. `.append(x)` always adds exactly one new slot containing `x` itself, whatever it is. `.extend(iterable)` instead iterates over its argument and appends each yielded item — meaning `.extend("abc")` adds three characters, not one string.

Python example
lst = [1, 2]
lst.append([3, 4])
print(lst)             # [1, 2, [3, 4]]

lst2 = [1, 2]
lst2.extend([3, 4])
print(lst2)            # [1, 2, 3, 4]

lst3 = [1, 2]
lst3.extend("ab")
print(lst3)            # [1, 2, 'a', 'b']

What to remember

What's the difference between .append() and .extend()?

Common footguns

  • Meaning to flatten one list into another but calling `.append()` by habit, ending up with an unwanted nested list.
lst = [1,2]
lst.append([3,4])   ->  [1, 2, [3,4]]    one new slot, holds a list
lst.extend([3,4])   ->  [1, 2, 3, 4]      unpacks, one slot per item

Core lesson 04

Shallow: new = old.copy() or old[:] (outer list is new, inner objects still shared). Deep: copy.deepcopy(old) (everything is new).

A shallow copy creates a new outer container but fills it with references to the *same* inner objects as the original — mutating a nested list still affects both copies. A deep copy recursively copies every nested object too, so the two structures become fully independent, at the cost of more time and memory.

Python example
import copy
original = [[1, 2], [3, 4]]

shallow = original.copy()
shallow[0].append(99)
print(original)          # [[1, 2, 99], [3, 4]]  <- original changed too!

deep = copy.deepcopy(original)
deep[0].append(-1)
print(original)          # unaffected by the deep copy edit

What to remember

How do you shallow-copy vs deep-copy a list of nested lists?

Common footguns

  • Assuming `.copy()` or `list[:]` gives full independence — it only protects the outer list, not anything nested inside.
original = [[1,2], [3,4]]

shallow = original.copy()      deep = copy.deepcopy(original)
shallow --+  +--[1,2]--+        deep ----+  +--[1,2]'-+ (new)
           \/                             \/
original --+                    original --+
           \                                \
            +--[3,4]--+ (shared!)            +--[3,4]'-+ (new)

Python lab

Browser Python lab

Runtime · idle

Python loads on your first run. Your code stays in this browser.

Best practices

  • Prefer list comprehensions for simple transforms/filters, but avoid deeply nested ones — readability first.
  • Use tuples for fixed, heterogeneous data; lists for homogeneous, growable collections.
  • Don't mutate a list while iterating over it directly — iterate a copy or build a new list.
  • Use collections.namedtuple or a dataclass instead of a bare tuple when field meaning matters.

Apply the concept in Interview practice

Merge Sorted ArrayeasyLeetCode #88 · O(m+n) time, O(1) space

Merge from the back using three pointers (ends of both arrays plus a write pointer) to avoid overwriting unread values.

Open problem
Rotate ArraymediumLeetCode #189 · O(n) time, O(1) space

Reverse the whole array, then reverse the first k elements, then reverse the rest — rotates in place.

Open problem
2D Array - DSeasyHackerRank · O(n) time

Slide a 3x3 'hourglass' window across the 6x6 grid, summing each and tracking the maximum.

Open problem
Remove Duplicates from Sorted ArrayeasyLeetCode #26 · O(n) time, O(1) space

Two pointers: a slow pointer marks the last unique position, a fast pointer scans ahead, copying forward only when a new value is found.

Open problem
Move ZeroeseasyLeetCode #283 · O(n) time, O(1) space

Two pointers: slide all non-zero elements to the front in order, then fill the remaining positions with zeroes.

Open problem

Concept checks

Q01

What's the key structural difference between a list and a tuple?

Q02

What does [x**2 for x in range(5) if x % 2 == 0] produce?

Q03

What's the difference between .append() and .extend()?

Q04

How do you shallow-copy vs deep-copy a list of nested lists?