PYTHON / LISTS AND TUPLES
Searching, counting, and sorting lists
Find, tally, and order list elements using in, index, count, sort, and sorted, including key functions and reverse.
What you will learn
- Use `in` for membership, `.index(x)` for the first position, `.count(x)` for tallies
- `.sort()` reorders in place and returns None; `sorted()` returns a new list
- Pass `key=` a one-argument function; Python compares its results, not the elements
- Python's sort is stable, so items with equal keys keep their original order
Understanding Searching, counting, and sorting lists
Searching a list means walking it from the front and comparing each element with `==`. That is exactly what the three search tools do: `x in data` gives a bool, `data.index(x)` gives the position of the leftmost match, and `data.count(x)` gives how many elements compare equal to `x`. The important asymmetry is failure behaviour: `in` returns False and `count` returns 0 when nothing matches, but `index` raises `ValueError`, so it needs either a guard or a `try`. `index` also accepts optional start and stop bounds, which is how you find the second, third, or later occurrence of a duplicate.
Sorting comes in two flavours that differ in what they touch. `data.sort()` rearranges the existing list object and returns `None`; `sorted(data)` leaves the original untouched and hands back a brand new list. Python returns `None` from mutating methods on purpose, so that `best = data.sort()` fails loudly (you get `None`) instead of quietly making you believe you have a sorted copy. `sorted` is also a builtin function rather than a list method, so it works on tuples, strings, dict views, and generators, always producing a list.
Both forms accept `key` and `reverse`. The `key` argument is a function called once per element; Python then orders elements by the returned values and never compares the elements themselves, which is why `sorted(words, key=len)` sorts by length while still giving you back the words. Ties are resolved by original position because the sort is stable, so sorting by one field twice is a legitimate way to sort by two fields. If elements are not mutually comparable, such as a mix of `int` and `str`, sorting raises `TypeError` at the first comparison that has no defined `<`.
scores = [88, 72, 95, 72, 61, 95, 80]
print(95 in scores)
print(scores.index(72))
print(scores.index(95, 3))
print(scores.count(95))
ranked = sorted(scores, reverse=True)
print(ranked)
print(scores)
scores.sort()
print(scores)
print(scores.sort())Search methods compare elements with `==` and report position or count, while sorting compares whatever `key` returns — with `sort()` mutating the list and `sorted()` building a new one.
Worked examples
Sorting with a key function
Shows how a key changes the ordering criterion and how stable sorting handles ties.
words = ["pear", "Fig", "apple", "Kiwi", "plum", "banana"]
print(sorted(words))
print(sorted(words, key=str.lower))
print(sorted(words, key=len))Example explained
Line 1The default sort compares strings by character code, and every uppercase letter is below every lowercase one, so 'Fig' and 'Kiwi' come first.
Line 2`key=str.lower` makes Python compare lowercased copies, giving case-insensitive alphabetical order while the original spellings are returned.
Line 3`key=len` compares only the lengths, so 'pear', 'Kiwi', and 'plum' all tie at 4.
Line 4Those three tied items appear in their original relative order because Python's sort is stable.
Searching without crashing
Guards index() with a membership test and shows the error it raises when the search window excludes the value.
names = ["ada", "grace", "alan", "grace"]
target = "linus"
if target in names:
print(names.index(target))
else:
print(target, "not found")
print(names.index("grace"))
print(names.index("grace", 2))
try:
names.index("ada", 1)
except ValueError as err:
print("error:", err)Example explained
Line 1`target in names` is the cheap safe check; without it, `index("linus")` would raise and stop the program.
Line 2`names.index("grace")` returns 1, the leftmost match, even though 'grace' also sits at index 3.
Line 3Passing a start of 2 restricts the scan to indexes 2 and up, which is how you reach the second occurrence.
Line 4`index("ada", 1)` fails because 'ada' only exists at index 0, and the message says 'is not in list' even though the value is present outside the window.
Ordering records by two fields
Uses a tuple-returning key to sort by score descending and name ascending, then picks the top record with max().
rows = [("ada", 91), ("bob", 78), ("cid", 91), ("dee", 60)]
by_score = sorted(rows, key=lambda r: (-r[1], r[0]))
for name, score in by_score:
print(name, score)
print(max(rows, key=lambda r: r[1]))
print(min(rows, key=lambda r: r[1]))Example explained
Line 1The key returns a tuple, and tuples compare element by element, so the score decides first and the name breaks ties.
Line 2Negating the score gives a descending score without `reverse=True`, which would also have reversed the name ordering.
Line 3`max` and `min` accept the same `key` argument, so you can find the best record without sorting the whole list.
Line 4`max` returns the first of several equal maxima, which is why ('ada', 91) wins over ('cid', 91).
Important notes
`.sort()` exists only on lists, while `sorted()` accepts any iterable (tuple, string, dict view, generator) and always returns a list.
Searching compares with `==`, so `[1, True, 1.0].count(1)` is 3 — `True` and `1.0` are equal to `1` even though they are different objects.
Common mistakes
Writing `scores = scores.sort()`: the name now refers to `None`, so the next `scores.append(1)` fails with AttributeError: 'NoneType' object has no attribute 'append'.
Calling `data.index(value)` for a value that may be absent: instead of a sentinel like -1 you get an uncaught ValueError that terminates the program.
Passing `key=len(word)` or `key=str.lower()` instead of `key=len` / `key=str.lower`: you must hand over the function itself, not the result of calling it, or you get a TypeError immediately.
Try it yourself
Change, predict, then run
Given `cities = ["Oslo", "Lima", "Oslo", "Bern", "Cairo"]`, print whether "Oslo" is present, the index of its first occurrence, how many times it appears, and a copy sorted by name length from longest to shortest — then print `cities` to prove it is unchanged.
Open the Python workspaceCheck your understanding
With `pairs = [("b", 2), ("a", 2), ("c", 1)]`, what does `sorted(pairs, key=lambda p: p[1])` return?
- [('c', 1), ('b', 2), ('a', 2)]
- [('c', 1), ('a', 2), ('b', 2)]
- [('a', 2), ('b', 2), ('c', 1)]
- None, because sorted() sorts the list in place
Show answer
The key extracts only the numbers, so ('b', 2) and ('a', 2) tie; a stable sort keeps tied items in their original order, leaving 'b' before 'a'. Option 1 assumes Python falls back to comparing the tuples themselves, which never happens once a key is supplied. Option 3 confuses `sorted()` with `list.sort()` — `sorted()` always returns a new list.