Lists: indexing, slicing, methods

RoadmapsPython

Overview

A list is an ordered, mutable sequence that can hold any Python objects. Create one with square brackets and access elements by zero-based index — negative indexes count from the end, and reading past the end raises `IndexError`: items = ['a', 'b', 'c', 'd'] items[0] # 'a' — first items[-1] # 'd' — last items[10] # IndexError — indexing past the end raises Slicing returns a NEW list and never modifies the original. Unlike indexing, out-of-range slice bounds are silently clamped, not errors: items[1:3] # ['b', 'c'] — index 1 up to (not including) 3 items[:2] # ['a', 'b'] — omitted start means 0 items[2:] # ['c', 'd'] — omitted stop means to the end items[::-1] # ['d', 'c', 'b', 'a'] — reversed copy items[1:99] # ['b', 'c', 'd'] — clamped, no error Key methods — each one mutates the list in place and returns `None`, except `pop()` and `index()` which return a value: - `append(x)` — add `x` as ONE item at the end (even if `x` is itself a list) - `extend(iterable)` — add each item from an iterable; `lst.extend([4, 5])` adds two items, `lst.append([4, 5])` adds one nested list - `insert(i, x)` — insert `x` before index `i`, shifting the rest right - `pop(i)` — remove AND return the item at index `i` (default: the last one); raises `IndexError` on an empty list - `remove(x)` — remove the first occurrence of `x`; raises `ValueError` if `x` is absent - `index(x)` — return the first index of `x`; raises `ValueError` if absent - `sort()` — sort in place and return `None`; use `sorted(lst)` when you need a new sorted list back Copying: `items[:]`, `items.copy()`, and `list(items)` are three spellings of the same operation — a shallow copy. You get a new outer list, but every slot still points to the SAME inner objects. With flat lists of numbers or strings that never matters; with nested mutable data it bites: a = [[1, 2], 20, 30] c = a[:] # new outer list, same inner references c[1] = 999 # REPLACES a slot — only c changes c[0].append(99) # mutates the shared INNER list print(a) # [[1, 2, 99], 20, 30] — a changed too! The rule: replacing a slot (`c[1] = 999`) touches only the copy; mutating the object INSIDE a slot (`c[0].append(99)`) is visible through both lists. The full fix (`copy.deepcopy`) is covered later in shallow-vs-deep-copy — for now, remember that slicing copies the row of labels, not the objects they point to. Think of a list as a numbered row of stickers, each pointing at an object: indexing reads one sticker, slicing photocopies a stretch of stickers (the objects themselves are not copied), and the mutating methods rearrange or replace stickers in place.

Lists are the default container for ordered data in Python: API response arrays, database result sets, pipeline stages, log entries. Knowing slicing and the right method prevents off-by-one bugs and unexpected None returns, and knowing that every list copy is shallow prevents the classic bug where mutating a 'copy' silently corrupts the original payload.

Where used: FastAPI response arrays of records, SQLModel query result lists, Collecting pipeline stage outputs

Why learn this

Code walkthrough

log = ['INFO', 'DEBUG', 'ERROR', 'INFO', 'WARN']
print(log[0])
print(log[-1])
print(log[1:3])
log.append('ERROR')
log.sort()
print(log)

Focus: log[1:3] returns a new list without changing log; append and sort mutate log in place — the final print shows both effects accumulated.

Aha moment

batches = [['job-1', 'job-2'], 'queued', 'low-priority']
snapshot = batches[:]
snapshot[0].append('job-99')
snapshot[1] = 'running'
print(batches)
print(snapshot)

Prediction: snapshot was made with [:] — a copy. What does the first print show for batches?

Common guess: [['job-1', 'job-2'], 'queued', 'low-priority'] — the copy is independent, so batches is untouched

Slicing is a SHALLOW copy: a new outer list whose slots point at the same inner objects. snapshot[0].append() mutates the shared inner list, so batches sees 'job-99' too; snapshot[1] = 'running' only rebinds a slot in the copy, so batches keeps 'queued'.

Common mistakes

Glossary

mutable
An object whose internal state or data can be changed after it is created, e.g. `items = [1, 2]; items.append(3)` modifies the same list.
shallow copy
A copy that duplicates the outer container but keeps references to the same inner objects — `a[:]`, `a.copy()`, and `list(a)` all produce one.
pagination
The process of dividing a large set of results into smaller, more manageable pages, typically used in web APIs, e.g. `results[0:10]` for page 1, `results[10:20]` for page 2.

Recall questions

Understanding checks

What does this code print?

['b', 'c']

Slicing a list (`items[1:]`) creates a *new* list. Modifying the original `items` list by appending 'd' does not affect the `subset` list.

Why does this code print `None` instead of `['hello', 'world']`?

Because `sort()` mutates the list in place and returns `None`.

List methods like `sort()` and `append()` change the original list rather than returning a new one. To fix it, call `words.sort()` as a statement, then print `words`.

What does this print — 3 or 4?

3

`append()` adds its argument as ONE item, so the list `['clean', 'store']` becomes a single nested element. To add each item separately, use `extend()` — then `len()` would be 4.

Practice tasks

Fix the pagination logic

The `paginate` function currently returns all items starting from `page * page_size`, but it doesn't limit the returned list to `page_size` elements. Modify the slice so it correctly returns a list of exactly `page_size` elements for the given page.

Challenge

Request queue manager

Write enqueue(queue, item) and dequeue(queue) functions that treat a list as a FIFO queue. enqueue appends to the end; dequeue removes and returns from the front, returning None when the queue is empty (do not raise). Print three enqueue calls, then dequeue until empty.

Continue learning

Previous: Primitive types: int, float, bool, str

Previous: Mutable vs immutable objects

Previous: for and while loops

Next: List Comprehensions

Next: Shallow vs Deep Copy

Next: Tuples: immutability and use cases

Return to Python Roadmap