Chapter 3 · Junior Developer

Lists: indexing, slicing, methods

A list is an ordered, changeable (mutable) collection: ["a", "b", "c"]. It can hold any types, mixed, and it remembers the order you put things in.

Indexing reaches one item by position, starting at 0: tasks[0] is the first. Negative indexes count from the end: tasks[-1] is the last. An index that doesn't exist raises IndexError.

Slicing takes a sub-list with list[start:stop]stop is *excluded*. tasks[:2] is the first two, tasks[1:] is everything from index 1 on, tasks[:] is a full copy.

The everyday methods:

  • .append(x) — add x to the end.
  • .insert(i, x) — put x at position i.
  • .pop(i) — remove *and return* the item at i (default: the last).
  • .remove(x) — delete the first item equal to x (errors if absent).
  • x in listTrue if x is present.

Lists are mutable: methods like .append change the list in place. And a list variable is a *reference* — b = a makes b point at the same list, so editing one shows up in the other. Use a[:] or list(a) for an independent copy.

Syntax

items = ["a", "b", "c"]
items[0]      # 'a'   (first)
items[-1]     # 'c'   (last)
items[1:]     # ['b', 'c']   (slice; stop excluded)
items[:2]     # ['a', 'b']

items.append("d")   # add to end
items.insert(0, "z")# add at position
items.pop(0)        # remove + return index 0
items.remove("b")   # delete first matching value
"a" in items        # membership test

Worked examples

Indexing and slicing

tasks = ["deploy", "test", "review"]
print(tasks[0])    # deploy   (first)
print(tasks[-1])   # review   (last)
print(tasks[:2])   # ['deploy', 'test']   (first two)
print(tasks[1:])   # ['test', 'review']   (from index 1)

Mutating methods + membership

queue = ["a", "b", "c"]
queue.append("d")        # add to end
first = queue.pop(0)     # first = "a"; removed from front
queue.remove("c")        # delete by value
queue.insert(0, "x")     # put "x" at the front
print(queue, first)
# Output:
# ['x', 'b', 'd'] a
print("b" in queue)      # True

A list is a reference — copy with [:] to stay independent

a = [1, 2, 3]
b = a            # same list, not a copy
b.append(4)
print(a)         # [1, 2, 3, 4]  ← a changed too!

c = a[:]         # an independent copy
c.append(5)
print(a)         # [1, 2, 3, 4]  ← unchanged this time

Common mistakes & gotchas

IndexError on an out-of-range index

items[5] on a 3-item list raises IndexError: list index out of range. Indexing is 0-based, so the last valid index is len(items) - 1. Slicing, by contrast, never errors on out-of-range bounds — items[:99] just gives what's there.

.pop() returns the item, .remove() returns None

.pop(i) hands back the removed item (useful: task = queue.pop(0)). .remove(x) returns None and deletes by *value*, raising ValueError if x isn't present — guard with if x in list: first.

Assignment copies the reference, not the list

b = a makes both names point at the *same* list, so mutating through one is visible through the other. For a separate list use a[:], list(a), or a.copy().

Why it matters

Lists are the default container for "several of something" — rows from a query, lines from a file, items in a cart, results to process. The indexing/slicing/append/pop vocabulary here is among the most-typed code in all of Python, and the reference-vs-copy gotcha is behind a huge share of "why did my other variable change?" bugs.