Notifications

No notifications

/Phase 2

Lists & Tuples

Lists & Tuples in Python 📋

Lists are Python's most versatile data structure — ordered, mutable collections that can hold mixed types.

Creating Lists

nums = [1, 2, 3, 4, 5]
mixed = [1, "hello", 3.14, True]
empty = []

Indexing & Slicing

OperationSyntaxResult (for [10,20,30,40,50])
First elementlst[0]10
Last elementlst[-1]50
Slicelst[1:3][20, 30]
Step slicelst[::2][10, 30, 50]
Reverselst[::-1][50, 40, 30, 20, 10]

Common List Methods

MethodDescription
append(x)Add item to end
insert(i, x)Insert at index i
remove(x)Remove first occurrence of x
pop(i)Remove & return item at index (default last)
sort()Sort in place
reverse()Reverse in place
extend(lst)Add all items from another list

List Comprehensions

A concise way to create lists:

squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]

Tuples

Tuples are ordered, immutable sequences. Use them for fixed collections:

point = (3, 7)
x, y = point          # Tuple unpacking
rgb = (255, 128, 0)

FeatureListTuple
Mutable✅ Yes❌ No
Syntax[1, 2](1, 2)
Use caseDynamic dataFixed records
Hashable❌ No✅ Yes (if elements are)

> Tip: Use sorted() to get a new sorted list without modifying the original. len() works on both lists and tuples.

On this page

Detailed Theory

Lists and tuples are Python's two main *ordered sequences*. A list is a flexible, growable container; a tuple is its immutable, lighter sibling. Together they handle 80% of "hold a bunch of things in order" jobs.

What a List Actually Is

fruits = ["apple", "banana", "cherry"]

A list is an ordered, mutable, indexable collection. You can add, remove, replace, sort, and slice items. Internally it's a dynamic array of pointers — contiguous memory that grows as needed.

What a Tuple Actually Is

point = (3, 4)

A tuple is the same shape but immutable — you can't change items after creation. Useful for fixed records (coords, RGB triples), dict keys, and function returns.

Indexing & Slicing

items = [10, 20, 30, 40, 50]
items[0]      # 10  — first
items[-1]     # 50  — last
items[1:4]    # [20, 30, 40]   — half-open
items[:3]     # [10, 20, 30]
items[::2]    # [10, 30, 50]   — every other
items[::-1]   # reversed copy

Slicing always returns a new list (copy). Negative indices count from the end. a[start:stop:step] — stop is exclusive.

Daily List Methods

lst.append(x)        # add to end
lst.extend(other)     # add many (like += other)
lst.insert(i, x)      # insert at index
lst.pop()             # remove & return last (default)
lst.pop(0)            # remove & return first (slow, O(n))
lst.remove(x)         # remove first occurrence of value
lst.sort()            # in-place sort
lst.reverse()         # in-place reverse
lst.count(x)          # how many
lst.index(x)          # first position of x
sorted(lst)           # NEW sorted list (doesn't mutate)
len(lst), x in lst    # size, membership

Beginner Mistakes to Skip

1. b = a then mutating. Both names point to the same list. Use b = a.copy() (or a[:] / list(a)) for an independent copy. 2. def f(items=[]):. Default is shared across calls. Use items=None and assign inside. 3. Inserting at position 0 in a hot loop. insert(0, x) is O(n). Use collections.deque for fast both-end ops. 4. if x in big_list repeatedly. O(n) each time. If you do this often, use a set. 5. a == b vs a is b for lists. == compares values; is compares identity. You almost always want ==. 6. (x) is not a tuple. It's just x in parens. Use (x,) for a 1-tuple.

Intermediate: Unpacking & Star-Unpacking

x, y = (3, 4)                # tuple unpacking
first, *rest = [1, 2, 3, 4]   # first=1, rest=[2,3,4]
head, *mid, tail = range(5)   # head=0, mid=[1,2,3], tail=4
a, b = b, a                    # swap, no temp variable

Works with any iterable. Star-unpacking on the LHS is a Python superpower.

Intermediate: List Comprehensions

squares    = [x*x for x in range(10)]
evens      = [x for x in nums if x % 2 == 0]
flattened  = [item for row in matrix for item in row]
mapped     = [f(x) if x else 0 for x in items]

Faster than for + append (loop runs in C). Don't nest more than two levels — readability collapses fast.

Intermediate: sort() with key=

people.sort(key=lambda p: p["age"])           # by age
people.sort(key=lambda p: (p["city"], -p["age"]))  # multi-key, age desc
import operator
people.sort(key=operator.itemgetter("age"))    # often faster than lambda

Sorting is stable — equal keys keep their relative order. Use sorted(...) for a fresh list, .sort() to mutate.

Intermediate: Tuples as Lightweight Records

user = ("alice", 30, "admin")
name, age, role = user

For more readability, reach for collections.namedtuple or a @dataclass. Tuples are also the only collection (with frozenset) you can use as a dict key.

Advanced: Time Complexity Cheat-Sheet

OpCost
a[i], lenO(1)
append, pop()O(1) amortised
insert(0, x), pop(0), removeO(n)
x in aO(n)
a + b, slicingO(k) (copies)
sortO(n log n)

Left-end operations are slow because every element shifts. Use collections.deque for fast appendleft / popleft.

Advanced: Shallow vs Deep Copy

import copy
shallow = original.copy()         # or list(original) / original[:]
deep    = copy.deepcopy(original) # recursively copies nested objects

Shallow copy duplicates the outer list but shares inner objects. Mutating a nested list in shallow will mutate it in original too. Reach for deepcopy only when you really need it (it's expensive).

Advanced: When Not to Use a List

  • Many membership checks → set (O(1)).
  • Need to look up by key → dict.
  • Fast both-ended queue → collections.deque.
  • Numerical arrays → numpy.ndarray (vectorised + tiny memory).
  • Need *immutable* → tuple or frozenset.
Picking the right collection often beats clever algorithms.

Advanced: Slicing Tricks

lst[:] = []           # clear in place (keeps same object)
lst[:] = other        # replace contents in place
lst[2:2] = [99, 100]  # insert without copying
lst[::-1]              # reversed copy

Assignment to a slice mutates the original list — useful when other code holds a reference to it.

Practice Path

1. Build a list of squares 0–9 using a comprehension; verify with == against a manual list. 2. Sort a list of dicts by two keys (e.g., city ascending, age descending) using sorted + key. 3. Demonstrate the shared-default-argument bug with def f(x, items=[]):, then fix it with items=None. 4. Replace a slow x in big_list check inside a loop by converting big_list to a set first; time both with time.perf_counter().