Lists & Sequences
1 · The lesson
readA list is an ordered, mutable collection of anything. It is the container you will use most in Python. When you are not sure which data structure you need, start with a list. You can change it later.
You met indexing and slicing in Strings. Lists use the same syntax, with one big difference: you can change a list in place.
1. What a List Is
nums = [1, 2, 3, 4] # all the same type (the usual case) mixed = [1, "two", 3.0, True, None] # mixed types, also allowed empty = [] # zero items
Three things to remember:
- Ordered: items keep the order you put them in.
nums[0]is always1. - Mutable: you can change, add and remove items after you create the list.
- Heterogeneous: items don't have to be the same type. Don't do this often, because a list of mixed types is harder to work with.
2. Creating Lists
a = [1, 2, 3] # written out directly (most common) b = list("hello") # ['h', 'e', 'l', 'l', 'o'] (one item per character) c = list(range(5)) # [0, 1, 2, 3, 4] d = [0] * 5 # [0, 0, 0, 0, 0] (one value repeated)
[x] * n is a quick way to make a list of a fixed size filled with an immutable value, such as 0. With a mutable value, such as a list, it causes a bug. See Common Mistakes below.
3. Indexing & Slicing
Indexing and slicing work exactly as they do for strings.
letters = ['a', 'b', 'c', 'd', 'e'] print(letters[0]) # 'a' (first) print(letters[-1]) # 'e' (last) print(letters[1:4]) # ['b', 'c', 'd'] (from 1 up to but not including 4) print(letters[::2]) # ['a', 'c', 'e'] (every other item) print(letters[::-1]) # ['e', 'd', 'c', 'b', 'a'] (a reversed copy)
The difference from strings: you can assign a new value to a position in a list.
letters[0] = 'X' print(letters) # ['X', 'b', 'c', 'd', 'e'] letters[1:3] = ['Y', 'Z', 'Q'] # assigning to a slice can change the length print(letters) # ['X', 'Y', 'Z', 'Q', 'd', 'e']
setup added so this can run · re-runs 1 earlier block on this page first
letters = ["alpha", "beta", "gamma"] # Earlier blocks on this page that this one builds on, re-run quietly first. _pm_earlier = [ "letters = ['a', 'b', 'c', 'd', 'e']\n\nprint(letters[0]) # 'a' (first)\nprint(letters[-1]) # 'e' (last)\nprint(letters[1:4]) # ['b', 'c', 'd'] (from 1 up to but not including 4)\nprint(letters[::2]) # ['a', 'c', 'e'] (every other item)\nprint(letters[::-1]) # ['e', 'd', 'c', 'b', 'a'] (a reversed copy)", ] def _pm_run_earlier(_srcs, _g): import sys as _s, io as _io, warnings as _w for _src in _srcs: _o, _e = _s.stdout, _s.stderr _s.stdout = _s.stderr = _io.StringIO() try: with _w.catch_warnings(): _w.simplefilter('ignore') exec(compile(_src, '<earlier block>', 'exec'), _g) except (Exception, SystemExit): pass finally: _s.stdout, _s.stderr = _o, _e if 'matplotlib.pyplot' in _s.modules: _s.modules['matplotlib.pyplot'].close('all') # their plots belong to their own blocks _pm_run_earlier(_pm_earlier, globals()) del _pm_earlier, _pm_run_earlier
On a string, both of those lines would raise a TypeError. On a list, they work.
4. Mutating Methods
These methods change the list in place and return None (except pop(), which returns the item it removed). They don't return a new list.
nums = [1, 2, 3] nums.append(4) # [1, 2, 3, 4] (adds one item to the end) nums.extend([5, 6]) # [1, 2, 3, 4, 5, 6] (adds every item from another list) nums.insert(0, 0) # [0, 1, 2, 3, 4, 5, 6] (inserts 0 at index 0) nums.remove(3) # [0, 1, 2, 4, 5, 6] (removes the first 3 it finds) last = nums.pop() # last == 6, nums == [0, 1, 2, 4, 5] (removes and returns the last item) first = nums.pop(0) # first == 0, nums == [1, 2, 4, 5] (removes and returns the item at index 0) nums.clear() # [] (empties the list)
Beginners often confuse append(x) and extend(iter). See Common Mistakes.
5. Sorting
There are two ways to sort. Choose the one you need.
nums = [3, 1, 4, 1, 5, 9, 2, 6] nums.sort() # sorts in place, so nums is now [1, 1, 2, 3, 4, 5, 6, 9] nums.sort(reverse=True) # sorts in place, largest first snapshot = [3, 1, 4, 1, 5] new = sorted(snapshot) # returns a new list, and snapshot is unchanged print(snapshot, new) # [3, 1, 4, 1, 5] [1, 1, 3, 4, 5]
The key= argument is the most useful part. It takes a function, and Python calls that function on each item to get the value to sort by.
words = ["banana", "fig", "cherry", "apple"] print(sorted(words, key=len)) # ['fig', 'apple', 'banana', 'cherry'] print(sorted(words, key=str.lower)) # alphabetical, ignoring upper and lower case people = [("Ada", 36), ("Linus", 54), ("Grace", 85)] print(sorted(people, key=lambda p: p[1])) # sort by age
key= calls your function once for each item, not once for each comparison, so it is fast.
6. Reverse: Two Ways
nums = [1, 2, 3] nums.reverse() # reverses in place, so nums is now [3, 2, 1] original = [1, 2, 3] for x in reversed(original): print(x) # 3, 2, 1 (but the original list is unchanged)
reversed() returns an iterator. That is fine in a for loop, but wrap it in list(...) if you need a list.
7. Built-ins That Take a List
nums = [4, 1, 7, 3, 9] print(len(nums)) # 5 print(min(nums)) # 1 print(max(nums)) # 9 print(sum(nums)) # 24
min and max accept a key= argument too, mirroring sorted.
8. Membership and Searching
fruits = ["apple", "banana", "cherry", "banana"] print("apple" in fruits) # True print("kiwi" not in fruits) # True print(fruits.count("banana")) # 2 print(fruits.index("cherry")) # 2 (the first index where "cherry" appears)
.index(x) raises ValueError if x isn't there. If you're not sure, check with in first or use try/except.
9. Copying: The Aliasing Trap
Beginners often spend hours on this bug.
a = [1, 2, 3] b = a # NOT a copy. b is another name for the same list b.append(4) print(a) # [1, 2, 3, 4] (a changed too)
b = a binds the name b to the same list object. Two names, one list. To get an independent copy:
a = [1, 2, 3] b = a.copy() # or list(a), or a[:] b.append(4) print(a, b) # [1, 2, 3] [1, 2, 3, 4]
.copy() makes a shallow copy: if the list contains other lists, those inner lists are still shared. For deeply nested structures, use copy.deepcopy() from the standard library.
10. The Immutable Version: Tuples
A tuple is a list that can't change.
point = (3, 4) # parentheses, comma-separated print(point[0]) # 3 # point[0] = 9 # TypeError: 'tuple' object does not support item assignment
Use tuples for fixed-shape records (a coordinate, an RGB colour, a database row) where mutation would be a bug. Tuples are also hashable, so they can be dictionary keys (as long as the items inside them are immutable too). Lists can't be dictionary keys. The Tuples lesson covers them in full.
11. A Quick Taste of Comprehensions
You'll write a lot of "take a list, transform every item, get a new list" code. The verbose way:
nums = [1, 2, 3, 4, 5] squares = [] for n in nums: squares.append(n * n) print(squares) # [1, 4, 9, 16, 25]
The Pythonic way is a list comprehension. It is one line, it is faster, and it is easy to read once you are used to it:
squares = [n * n for n in nums] evens = [n for n in nums if n % 2 == 0]
setup added so this can run · re-runs 6 earlier blocks on this page first
nums = [3, -1, 4, -1, 5] # Earlier blocks on this page that this one builds on, re-run quietly first. _pm_earlier = [ 'nums = [1, 2, 3, 4] # all the same type (the usual case)\nmixed = [1, "two", 3.0, True, None] # mixed types, also allowed\nempty = [] # zero items', 'nums = [1, 2, 3]\n\nnums.append(4) # [1, 2, 3, 4] (adds one item to the end)\nnums.extend([5, 6]) # [1, 2, 3, 4, 5, 6] (adds every item from another list)\nnums.insert(0, 0) # [0, 1, 2, 3, 4, 5, 6] (inserts 0 at index 0)\nnums.remove(3) # [0, 1, 2, 4, 5, 6] (removes the first 3 it finds)\nlast = nums.pop() # last == 6, nums == [0, 1, 2, 4, 5] (removes and returns the last item)\nfirst = nums.pop(0) # first == 0, nums == [1, 2, 4, 5] (removes and returns the item at index 0)\nnums.clear() # [] (empties the list)', 'nums = [3, 1, 4, 1, 5, 9, 2, 6]\n\nnums.sort() # sorts in place, so nums is now [1, 1, 2, 3, 4, 5, 6, 9]\nnums.sort(reverse=True) # sorts in place, largest first\n\nsnapshot = [3, 1, 4, 1, 5]\nnew = sorted(snapshot) # returns a new list, and snapshot is unchanged\nprint(snapshot, new) # [3, 1, 4, 1, 5] [1, 1, 3, 4, 5]', 'nums = [1, 2, 3]\n\nnums.reverse() # reverses in place, so nums is now [3, 2, 1]\n\noriginal = [1, 2, 3]\nfor x in reversed(original):\n print(x) # 3, 2, 1 (but the original list is unchanged)', 'nums = [4, 1, 7, 3, 9]\n\nprint(len(nums)) # 5\nprint(min(nums)) # 1\nprint(max(nums)) # 9\nprint(sum(nums)) # 24', 'nums = [1, 2, 3, 4, 5]\nsquares = []\nfor n in nums:\n squares.append(n * n)\nprint(squares) # [1, 4, 9, 16, 25]', ] def _pm_run_earlier(_srcs, _g): import sys as _s, io as _io, warnings as _w for _src in _srcs: _o, _e = _s.stdout, _s.stderr _s.stdout = _s.stderr = _io.StringIO() try: with _w.catch_warnings(): _w.simplefilter('ignore') exec(compile(_src, '<earlier block>', 'exec'), _g) except (Exception, SystemExit): pass finally: _s.stdout, _s.stderr = _o, _e if 'matplotlib.pyplot' in _s.modules: _s.modules['matplotlib.pyplot'].close('all') # their plots belong to their own blocks _pm_run_earlier(_pm_earlier, globals()) del _pm_earlier, _pm_run_earlier
Read it left-to-right: give me n*n, for each n in nums. A later lesson covers comprehensions in detail.
Common Mistakes
1. [x] * n with a mutable item: every slot shares the same object.
grid = [[]] * 3 # looks like three empty lists grid[0].append("oops") print(grid) # [['oops'], ['oops'], ['oops']] (all three changed!)
All three slots point to the same inner list. To get three independent lists:
grid = [[] for _ in range(3)] # the comprehension makes a new list on each loop
2. Mutating a list while iterating over it.
nums = [1, 2, 3, 4, 5] for n in nums: if n % 2 == 0: nums.remove(n) # skips items and gives the wrong result
Iterate over a copy (
for n in nums[:]:) or build a new list with a comprehension.
3. .append([1, 2]) vs .extend([1, 2]).
a = [0] a.append([1, 2]) # [0, [1, 2]] (one new item, which is a list) a = [0] a.extend([1, 2]) # [0, 1, 2] (two new items, taken out of the list)
append adds one thing. extend consumes an iterable.
4. Expecting .sort() to return the sorted list.
nums = [3, 1, 2] result = nums.sort() # result is None, because sort() changed nums in place print(result) # None
Use
sorted(nums) when you want the return value. Use nums.sort() when you want to mutate.
🎯 Your Turn: Move Zeros to the End
Write a function move_zeros(lst) that returns a new list with every 0 moved to the end, preserving the original order of the non-zero elements.
[1, 0, 3, 0, 5, 0, 7] → [1, 3, 5, 7, 0, 0, 0] [0, 0, 1] → [1, 0, 0] [1, 2, 3] → [1, 2, 3] [] → []
Skeleton:
def move_zeros(lst): # TODO 1: collect non-zero items in their original order # TODO 2: count how many zeros there were # TODO 3: return non-zeros followed by that many zeros ... print(move_zeros([1, 0, 3, 0, 5, 0, 7])) # [1, 3, 5, 7, 0, 0, 0]
Hint 1: Two passes is fine
You don't need a complicated solution. One pass to collect non-zero items, then append the right number of zeros. A list comprehension with a filter handles the first pass in one line:[x for x in lst if x != 0].
Hint 2: Count what's missing
The number of zeros islen(lst) - len(non_zeros), or just lst.count(0). Then non_zeros + [0] * zeros gives you the answer, because list concatenation builds a new list.
Show full solution
def move_zeros(lst): non_zeros = [x for x in lst if x != 0] zeros = len(lst) - len(non_zeros) return non_zeros + [0] * zeros # Sanity checks print(move_zeros([1, 0, 3, 0, 5, 0, 7])) # [1, 3, 5, 7, 0, 0, 0] print(move_zeros([0, 0, 1])) # [1, 0, 0] print(move_zeros([1, 2, 3])) # [1, 2, 3] print(move_zeros([])) # []
Only two lines do the work. The comprehension removes the zeros, and the concatenation adds them back at the end. The function returns a new list instead of changing the input. This is usually the safer choice, because the caller still has the original list.
You can also write an in-place version (move the non-zero items forward, then fill the end with zeros). It is a common interview answer when memory use matters. For everyday code, the version above reads better and runs in linear time too.
What You Learned
- A list is an ordered, mutable, heterogeneous sequence. It is Python's default container.
- Build with
[],list(iterable), or[x] * n(careful with mutables). - Indexing and slicing work as they do on strings, but you can also assign to an index or to a slice.
- Mutating methods (
append,extend,insert,remove,clear) returnNone, butpopreturns the item it removed. They all change the list in place. .sort()mutates, butsorted()returns a new list. Both acceptkey=andreverse=.b = ais aliasing, not copying. Use.copy(),list(a), ora[:]for an independent list.- Tuples are the immutable, hashable version of lists. Use them when the data shouldn't change.
- List comprehensions are the idiomatic way to transform a list into another list.
Next: Conditionals for making decisions, then Loops for working through your lists.
Practice this
on practicepython.inShort exercises that run in your browser and tell you what your code did, as well as whether a test passed.
Found this useful? Share it with a friend who’s learning Python.