C.W.K.
Stream
Lesson 01 of 07 · published

Lists — Python's Default Sequence

~22 min · list, indexing, slicing, sequence

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

The 80% collection

If you only ever learned one collection in Python, you'd reach for list 80% of the time and survive. It's an ordered, mutable, dynamically-sized sequence that holds anything — including other lists. Lists are written with square brackets and can be empty, homogeneous, or wildly mixed.

Creating lists

Three common ways: literal syntax, the list() constructor, and comprehensions (which we cover in lesson 6). Literal is the one you'll write most often.

Indexing — and Python's negative trick

Indexing starts at 0, like most languages. The trick Python adds: negative indices count from the end. my_list[-1] is the last element. my_list[-2] is the second-to-last. There's no special syntax — negative numbers just work.

Slicing — the syntax that quietly does the work of three for-loops

Slicing is my_list[start:stop:step]. Each part is optional. my_list[:5] is the first five. my_list[-3:] is the last three. my_list[::-1] is the whole list reversed. Slicing returns a new list — the original is untouched. This is one of the highest-leverage syntactic features in the language; learn it once and you'll skip writing dozens of loops over your career.

Lists are mutable — and that has a price

You can change a list after creation: append, extend, slice-assignment, del. That's the joy. The price: a list can't be a dict key, can't be inside a set, and if you pass a list to a function, the function can mutate your list.

Warning: The classic gotcha — def f(x=[]). The default list is shared across all calls because it's evaluated once, at function definition time. Use def f(x=None): if x is None: x = [] instead.

Copying — the shallow vs deep distinction

list2 = list1 doesn't copy — both names point at the same list. list2 = list1[:] or list2 = list1.copy() makes a shallow copy: a new outer list, but the inner objects are still shared. For nested structures, reach for copy.deepcopy().

Pythonic Way: If you mean "a sequence of things," reach for list. Don't reach for tuple just because the values won't change — there's a sharper rule for that, and we cover it in lesson 3.

Code

Three ways to create a list·python
# 1. Literal — the way you'll write 90% of the time
fruits = ["apple", "banana", "cherry"]

# 2. Constructor — turns any iterable into a list
letters = list("hello")          # ['h', 'e', 'l', 'l', 'o']
range_list = list(range(5))      # [0, 1, 2, 3, 4]

# 3. Empty, then build up
result = []
result.append(1)
result.append(2)
print(result)                    # [1, 2]
Negative indexing — counting from the end·python
scores = [88, 92, 75, 100, 64]

print(scores[0])    # 88   first element
print(scores[-1])   # 64   last element
print(scores[-2])   # 100  second-to-last

# IndexError if you go past the end in either direction
# print(scores[5])   # IndexError
# print(scores[-6])  # IndexError
Slicing — start : stop : step·python
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

print(nums[2:5])      # [2, 3, 4]      stop is EXCLUSIVE
print(nums[:3])       # [0, 1, 2]      from the start
print(nums[7:])       # [7, 8, 9]      to the end
print(nums[::2])      # [0, 2, 4, 6, 8]   every second
print(nums[::-1])     # [9, 8, ..., 0]  reversed
print(nums[-3:])      # [7, 8, 9]      last three

# Slicing always returns a NEW list. nums is untouched.
print(nums)           # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Slice ASSIGNMENT — replacing a range in place·python
data = [10, 20, 30, 40, 50]

# Replace 3 elements with 5 new ones
data[1:4] = ["a", "b", "c", "d", "e"]
print(data)           # [10, 'a', 'b', 'c', 'd', 'e', 50]

# Delete a slice
data[1:4] = []
print(data)           # [10, 'd', 'e', 50]

# Insert without removing — slice of length 0
data[2:2] = ["X", "Y"]
print(data)           # [10, 'd', 'X', 'Y', 'e', 50]
Aliases vs. shallow copies vs. deep copies·python
import copy

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

alias = original                     # SAME list — both names share
shallow = original[:]                # new outer, same inner refs
deep = copy.deepcopy(original)       # new outer AND new inner

original[0].append(99)

print(alias)    # [[1, 2, 99], [3, 4]]   — shares with original
print(shallow)  # [[1, 2, 99], [3, 4]]   — inner list is shared!
print(deep)     # [[1, 2], [3, 4]]       — fully independent
The mutable default argument trap·python
# DON'T DO THIS
def bad(item, history=[]):
    history.append(item)
    return history

print(bad("a"))   # ['a']
print(bad("b"))   # ['a', 'b']    <- history was REUSED
print(bad("c"))   # ['a', 'b', 'c']

# DO THIS
def good(item, history=None):
    if history is None:
        history = []
    history.append(item)
    return history

External links

Exercise

Create a list of 10 integers from 1 to 10. Then, using only slicing (no loops, no for, no comprehensions): (a) print the first 3, (b) print the last 3, (c) print every other element starting from the second, (d) print the list reversed. Confirm none of these mutate the original — print the original at the end and verify it's unchanged.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.