How to Sort a List of Objects by Attribute in Python

To sort a list of objects by an attribute in Python, use sorted(items, key=lambda x: x.score, reverse=True) or items.sort(...) with the same arguments. Python's built-in sort is O(n log n) and stable. A hand-written bubble sort - two nested loops that swap neighbours - is O(n^2) and becomes very slow once the list has a few thousand items.

Start this project - freeLive cloud workspace in your browser · lands on your portfolio
Backend Engineerpythonperformancebig-o

The slow version

This ranking function sorts users by score, highest first:

from dataclasses import dataclass

@dataclass
class User:
    id: int
    name: str
    score: float

def rank_users(users: list[User]) -> list[User]:
    for i in range(len(users)):
        for j in range(len(users) - 1):
            if users[j].score < users[j + 1].score:
                users[j], users[j + 1] = users[j + 1], users[j]
    return users

This is a bubble sort. It gives the right answer, and with 50 users nobody notices how slow it is. With a few thousand users, a search request starts to hang.

Why it is slow

The inner loop walks the whole list, and the outer loop repeats that once per user. For n users that is about n * n comparisons:

Users Comparisons (n x n)
1,000 1,000,000
5,000 25,000,000
10,000 100,000,000

Python's built-in sort (Timsort) needs about n log n comparisons - for 10,000 items that is in the order of 130,000 instead of 100,000,000. It is also written in C, so each comparison is cheaper too.

The fix: sorted() with a key

def rank_users(users: list[User]) -> list[User]:
    return sorted(users, key=lambda u: u.score, reverse=True)

Note that the old bubble sort changed the caller's list in place. If other code depended on that, use users.sort(...) and return users.

operator.attrgetter

The operator module has a ready-made key function that is a little faster than a lambda and easy to read:

from operator import attrgetter

ranked = sorted(users, key=attrgetter("score"), reverse=True)

For dictionaries, use itemgetter("score") in the same way.

Sorting by more than one field

Return a tuple from the key. Python compares the first element, then the second if the first is equal:

# highest score first, then name A-Z for equal scores
ranked = sorted(users, key=lambda u: (-u.score, u.name))

The minus sign reverses a number field without reverse=True, so the name can still sort A to Z. For fields you cannot negate, such as strings, sort in two passes - Python's sort is stable, so a second sort keeps the order of the first for equal keys:

ranked = sorted(users, key=attrgetter("name"))                  # secondary
ranked = sorted(ranked, key=attrgetter("score"), reverse=True)  # primary

What "stable" means

A stable sort keeps items with equal keys in their original order. If two users both have a score of 0.9, they stay in the order they had in the input. Python's sort is stable, including with reverse=True. This makes results predictable, which matters for tests and for pages where the order should not change between requests.

Measure before and after

time python3 -c "import random; from search import User, rank_users; rank_users([User(i, 'u', random.random()) for i in range(3000)])"

The bubble sort takes seconds; sorted() finishes almost at once. Then check the result, not only the speed:

import random
users = [User(i, "u", random.random()) for i in range(5000)]
ranked = rank_users(users)
assert all(ranked[i].score >= ranked[i + 1].score for i in range(len(ranked) - 1))
assert rank_users([]) == []

When you only need the top few

If a page shows only the top 10 of a large list, heapq.nlargest avoids sorting everything:

import heapq
top10 = heapq.nlargest(10, users, key=attrgetter("score"))

The wider lesson

Hand-written sorting loops in application code are almost always a mistake. When you review code, look for two nested loops over the same list with a swap inside - that is a bubble sort, even when it has a different name. Replace it with the built-in sort and a key function.

Do this project yourself. It's ready in a live cloud workspace in your browser - when you finish, it lands on your portfolio.

Start this project - free

What you'll practice

FAQ

How do I sort a list of objects by an attribute in Python?

Use sorted(items, key=lambda x: x.attribute) or items.sort(key=lambda x: x.attribute). operator.attrgetter("attribute") works as the key too.

How do I sort in descending order in Python?

Pass reverse=True to sorted() or list.sort(). For a number field inside a tuple key, you can also negate it, for example key=lambda u: (-u.score, u.name).

What is the difference between sorted() and list.sort()?

sorted() returns a new list and works on any iterable. list.sort() sorts the list in place and returns None. Both use the same algorithm and accept key and reverse.

Is Python's sort stable?

Yes. Items with equal keys keep their original order, also when you use reverse=True. This lets you sort by several fields in separate passes, secondary key first.

Why is bubble sort slow?

It compares and swaps neighbouring items over and over, which takes about n x n steps. Python's built-in sort takes about n log n steps, which is far less for large lists.

Keep learning

Remove Duplicates From a Python List and Keep OrderBackend projectWhy JavaScript Sorts Numbers as StringsBackend projectBuild a Top-N Report With Window FunctionsBackend projectBackend roadmapStep by step to hiredBackend interview questionsSTAR answersAll Backend projectsProjects hub

Do this project yourself. It's ready in a live cloud workspace in your browser - when you finish, it lands on your portfolio.

Start this project - free
Start this project - free