Python Basics — sorted() with Key

4 minute read

sorted() with Key

Python’s sorted() function is incredibly flexible. The key parameter lets you define custom sort orders using any function — including lambdas.

sorted() Basics

numbers = [5, 2, 8, 1, 9, 3]
sorted_numbers = sorted(numbers)
print(sorted_numbers)  # [1, 2, 3, 5, 8, 9]
print(numbers)         # [5, 2, 8, 1, 9, 3] — original unchanged

# Reverse order
desc = sorted(numbers, reverse=True)
print(desc)  # [9, 8, 5, 3, 2, 1]

sorted() returns a new list. The original is unchanged. To sort in-place, use list.sort().

The key Parameter

The key function is called on each element before comparison. It determines what value to sort by:

words = ["banana", "apple", "cherry", "date"]

# Sort by string length
by_length = sorted(words, key=len)
print(by_length)  # ['date', 'apple', 'banana', 'cherry']

# Sort alphabetically (case-insensitive)
mixed = ["Banana", "apple", "Cherry", "date"]
by_lower = sorted(mixed, key=str.lower)
print(by_lower)  # ['apple', 'Banana', 'Cherry', 'date']

Key with Lambda

# Sort by last character
words = ["hello", "world", "python", "java"]
by_last = sorted(words, key=lambda w: w[-1])
print(by_last)  # ['java', 'world', 'python', 'hello']

# Sort by absolute value
numbers = [-5, 3, -1, 8, -2, 7]
by_abs = sorted(numbers, key=lambda x: abs(x))
print(by_abs)  # [-1, -2, 3, -5, 7, 8]

# Shorter: use abs directly
by_abs = sorted(numbers, key=abs)
print(by_abs)  # [-1, -2, 3, -5, 7, 8]

Sorting Complex Objects

Lists of Tuples

students = [("Alice", 85), ("Bob", 92), ("Charlie", 78), ("Diana", 92)]

# Sort by score
by_score = sorted(students, key=lambda s: s[1])
print(by_score)
# [('Charlie', 78), ('Alice', 85), ('Bob', 92), ('Diana', 92)]

# Sort by score descending
by_score_desc = sorted(students, key=lambda s: s[1], reverse=True)
print(by_score_desc)
# [('Bob', 92), ('Diana', 92), ('Alice', 85), ('Charlie', 78)]

Lists of Dictionaries

employees = [
    {"name": "Alice", "salary": 75000, "department": "Engineering"},
    {"name": "Bob", "salary": 65000, "department": "Marketing"},
    {"name": "Charlie", "salary": 85000, "department": "Engineering"},
    {"name": "Diana", "salary": 70000, "department": "Marketing"},
]

# Sort by salary
by_salary = sorted(employees, key=lambda e: e["salary"])
for emp in by_salary:
    print(f"{emp['name']}: ₹{emp['salary']}")
# Bob: ₹65000
# Diana: ₹70000
# Alice: ₹75000
# Charlie: ₹85000

# Sort by department, then by salary within department
by_dept_salary = sorted(employees, key=lambda e: (e["department"], e["salary"]))
for emp in by_dept_salary:
    print(f"{emp['department']} — {emp['name']}: ₹{emp['salary']}")
# Engineering — Alice: ₹75000
# Engineering — Charlie: ₹85000
# Marketing — Bob: ₹65000
# Marketing — Diana: ₹70000

Multi-Key Sorting with Tuples

Return a tuple from the key function to sort by multiple criteria:

data = [
    ("Alice", "B", 85),
    ("Bob", "A", 92),
    ("Charlie", "B", 78),
    ("Diana", "A", 95),
]

# Sort by grade (ascending), then by score (descending)
# Negate the score to reverse just that field
result = sorted(data, key=lambda x: (x[1], -x[2]))
for name, grade, score in result:
    print(f"{grade}: {name} ({score})")
# A: Diana (95)
# A: Bob (92)
# B: Alice (85)
# B: Charlie (78)

operator.itemgetter and operator.attrgetter

The operator module provides efficient key functions:

from operator import itemgetter

students = [("Alice", 85), ("Bob", 92), ("Charlie", 78)]

# Same as key=lambda s: s[1]
by_score = sorted(students, key=itemgetter(1))
print(by_score)  # [('Charlie', 78), ('Alice', 85), ('Bob', 92)]

# Multi-key with itemgetter
data = [("Alice", "B", 85), ("Bob", "A", 92), ("Charlie", "B", 78)]
by_grade_name = sorted(data, key=itemgetter(1, 0))
print(by_grade_name)
# [('Bob', 'A', 92), ('Alice', 'B', 85), ('Charlie', 'B', 78)]
from operator import attrgetter

class Student:
    def __init__(self, name, gpa):
        self.name = name
        self.gpa = gpa
    def __repr__(self):
        return f"Student({self.name}, {self.gpa})"

students = [Student("Alice", 3.8), Student("Bob", 3.2), Student("Charlie", 3.9)]

by_gpa = sorted(students, key=attrgetter("gpa"), reverse=True)
print(by_gpa)  # [Student(Charlie, 3.9), Student(Alice, 3.8), Student(Bob, 3.2)]

Stable Sort Behavior

Python’s sort is stable — elements that compare equal maintain their original order:

data = [
    ("Alice", "Engineering"),
    ("Bob", "Marketing"),
    ("Charlie", "Engineering"),
    ("Diana", "Marketing"),
]

# Sort by department — Alice stays before Charlie, Bob stays before Diana
by_dept = sorted(data, key=lambda x: x[1])
print(by_dept)
# [('Alice', 'Engineering'), ('Charlie', 'Engineering'), ('Bob', 'Marketing'), ('Diana', 'Marketing')]

This makes multi-pass sorting safe:

# Sort by name first, then by department (stable sort preserves name order within departments)
result = sorted(data, key=lambda x: x[0])                  # first by name
result = sorted(result, key=lambda x: x[1])                # then by dept
print(result)
# [('Alice', 'Engineering'), ('Charlie', 'Engineering'), ('Bob', 'Marketing'), ('Diana', 'Marketing')]

sorted() vs list.sort()

Feature sorted() list.sort()
Returns New sorted list None (sorts in place)
Works on Any iterable Only lists
Original Unchanged Modified
Memory Creates new list No extra memory
numbers = [5, 2, 8, 1]

# sorted() — returns new list
new_list = sorted(numbers)
print(numbers)   # [5, 2, 8, 1] — unchanged
print(new_list)  # [1, 2, 5, 8]

# list.sort() — modifies in place
numbers.sort()
print(numbers)   # [1, 2, 5, 8] — changed

Summary

  • sorted(iterable, key=func, reverse=bool) returns a new sorted list
  • The key function determines what to sort by — called on each element
  • Use lambda for simple key functions, named functions for complex ones
  • Sort by multiple criteria using tuple keys: key=lambda x: (x[0], -x[1])
  • operator.itemgetter and attrgetter are efficient alternatives to lambdas
  • Python’s sort is stable — equal elements keep their original order

Next up: Lambda & Functional — Practice Exercises →

Updated: