Python - Sort List (sort vs sorted)

Python provides two built-in approaches for sorting: the `sort()` method and the `sorted()` function. The fundamental distinction lies in mutability and return values.

Key Insights

  • sort() modifies lists in-place and returns None, while sorted() creates a new sorted list and works with any iterable
  • Both methods use Timsort algorithm with O(n log n) complexity and support custom sorting via key and reverse parameters
  • Choose sort() for memory efficiency with large lists you don’t need to preserve, sorted() when you need the original data or are working with non-list iterables

Understanding the Core Difference

Python provides two built-in approaches for sorting: the sort() method and the sorted() function. The fundamental distinction lies in mutability and return values.

# sort() - modifies in place, returns None
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
result = numbers.sort()
print(numbers)  # [1, 1, 2, 3, 4, 5, 6, 9]
print(result)   # None

# sorted() - returns new list, original unchanged
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
result = sorted(numbers)
print(numbers)  # [3, 1, 4, 1, 5, 9, 2, 6]
print(result)   # [1, 1, 2, 3, 4, 5, 6, 9]

The sort() method only works on lists because it modifies the object directly. The sorted() function accepts any iterable and always returns a new list.

# sorted() works with any iterable
sorted_tuple = sorted((5, 2, 8, 1))  # [1, 2, 5, 8]
sorted_string = sorted("python")      # ['h', 'n', 'o', 'p', 't', 'y']
sorted_dict = sorted({3: 'c', 1: 'a', 2: 'b'})  # [1, 2, 3]

# sort() only works on lists
my_tuple = (5, 2, 8, 1)
# my_tuple.sort()  # AttributeError: 'tuple' object has no attribute 'sort'

Reverse Sorting

Both methods support reverse sorting through the reverse parameter.

numbers = [3, 1, 4, 1, 5, 9, 2, 6]

# Descending order with sort()
numbers.sort(reverse=True)
print(numbers)  # [9, 6, 5, 4, 3, 2, 1, 1]

# Descending order with sorted()
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
desc = sorted(numbers, reverse=True)
print(desc)  # [9, 6, 5, 4, 3, 2, 1, 1]

Custom Sorting with Key Functions

The key parameter accepts a function that extracts a comparison key from each element. This is where sorting becomes powerful.

# Sort strings by length
words = ["python", "is", "awesome", "for", "data"]
words.sort(key=len)
print(words)  # ['is', 'for', 'data', 'python', 'awesome']

# Sort case-insensitive
names = ["Alice", "bob", "Charlie", "david"]
sorted_names = sorted(names, key=str.lower)
print(sorted_names)  # ['Alice', 'bob', 'Charlie', 'david']

# Sort by absolute value
integers = [-5, 2, -8, 1, 9, -3]
integers.sort(key=abs)
print(integers)  # [1, 2, -3, -5, -8, 9]

Sorting Complex Data Structures

When working with dictionaries or objects, the key parameter becomes essential.

# Sort list of dictionaries
employees = [
    {"name": "Alice", "age": 30, "salary": 70000},
    {"name": "Bob", "age": 25, "salary": 50000},
    {"name": "Charlie", "age": 35, "salary": 90000}
]

# Sort by age
employees.sort(key=lambda x: x["age"])
print([e["name"] for e in employees])  # ['Bob', 'Alice', 'Charlie']

# Sort by salary descending
sorted_by_salary = sorted(employees, key=lambda x: x["salary"], reverse=True)
print([e["name"] for e in sorted_by_salary])  # ['Charlie', 'Alice', 'Bob']

For custom objects, define comparison logic:

class Product:
    def __init__(self, name, price, rating):
        self.name = name
        self.price = price
        self.rating = rating
    
    def __repr__(self):
        return f"Product({self.name}, ${self.price}, {self.rating}★)"

products = [
    Product("Laptop", 1200, 4.5),
    Product("Mouse", 25, 4.8),
    Product("Keyboard", 80, 4.3)
]

# Sort by price
products.sort(key=lambda p: p.price)
print(products)
# [Product(Mouse, $25, 4.8★), Product(Keyboard, $80, 4.3★), Product(Laptop, $1200, 4.5★)]

# Sort by rating descending
by_rating = sorted(products, key=lambda p: p.rating, reverse=True)
print(by_rating)
# [Product(Mouse, $25, 4.8★), Product(Laptop, $1200, 4.5★), Product(Keyboard, $80, 4.3★)]

Multi-Level Sorting

Sort by multiple criteria using tuples in the key function. Python compares tuples element-by-element.

students = [
    ("Alice", "A", 95),
    ("Bob", "B", 85),
    ("Charlie", "A", 92),
    ("David", "B", 88),
    ("Eve", "A", 95)
]

# Sort by grade, then score descending
students.sort(key=lambda x: (x[1], -x[2]))
print(students)
# [('Eve', 'A', 95), ('Alice', 'A', 95), ('Charlie', 'A', 92), 
#  ('David', 'B', 88), ('Bob', 'B', 85)]

For more complex scenarios, use operator.itemgetter or operator.attrgetter:

from operator import itemgetter, attrgetter

# Sort by multiple dictionary keys
data = [
    {"dept": "Sales", "name": "Alice", "score": 95},
    {"dept": "IT", "name": "Bob", "score": 88},
    {"dept": "Sales", "name": "Charlie", "score": 92},
    {"dept": "IT", "name": "David", "score": 91}
]

data.sort(key=itemgetter("dept", "score"), reverse=True)
print([(d["dept"], d["name"], d["score"]) for d in data])
# [('Sales', 'Alice', 95), ('Sales', 'Charlie', 92), 
#  ('IT', 'David', 91), ('IT', 'Bob', 88)]

Performance Considerations

Both methods use Timsort, optimized for real-world data with O(n log n) worst-case complexity. The difference lies in memory usage.

import sys

# Memory comparison
large_list = list(range(1000000, 0, -1))
original_size = sys.getsizeof(large_list)

# sort() - no additional list created
large_list.sort()
# Memory used: original_size

# sorted() - creates new list
large_list = list(range(1000000, 0, -1))
new_list = sorted(large_list)
# Memory used: original_size + sys.getsizeof(new_list)

For large datasets where you don’t need the original order, sort() is more memory-efficient. Use sorted() when you need to preserve the original or when working with immutable iterables.

Stability in Sorting

Both methods provide stable sorting—elements with equal keys maintain their relative order.

# Stable sort example
records = [
    ("Alice", 25),
    ("Bob", 30),
    ("Charlie", 25),
    ("David", 30)
]

# Sort by age - original order preserved for equal ages
sorted_records = sorted(records, key=lambda x: x[1])
print(sorted_records)
# [('Alice', 25), ('Charlie', 25), ('Bob', 30), ('David', 30)]

This stability is crucial when performing multiple sorts:

# Sort by secondary criterion first, then primary
employees = [
    {"name": "Alice", "dept": "IT", "salary": 70000},
    {"name": "Bob", "dept": "Sales", "salary": 70000},
    {"name": "Charlie", "dept": "IT", "salary": 80000}
]

# First sort by name
employees.sort(key=lambda x: x["name"])
# Then sort by salary (stable, preserves name order within same salary)
employees.sort(key=lambda x: x["salary"])
print([(e["name"], e["salary"]) for e in employees])
# [('Alice', 70000), ('Bob', 70000), ('Charlie', 80000)]

Practical Decision Matrix

Use sort() when:

  • Working exclusively with lists
  • Memory efficiency is critical
  • You don’t need the original order
  • Modifying in-place is acceptable

Use sorted() when:

  • Working with any iterable (tuples, strings, sets, generators)
  • You need to preserve the original collection
  • Creating intermediate sorted versions
  • The function call style is more readable in your context
# Practical example: filtering and sorting
data = [15, 8, 23, 4, 42, 16, 11]

# Keep original, work with filtered sorted version
evens_sorted = sorted(x for x in data if x % 2 == 0)
print(f"Original: {data}")      # [15, 8, 23, 4, 42, 16, 11]
print(f"Evens: {evens_sorted}") # [4, 8, 16, 42]

# Or modify in-place when original isn't needed
data = [x for x in data if x % 2 == 0]
data.sort()
print(f"Modified: {data}")      # [4, 8, 16, 42]

Both methods are highly optimized and suitable for production use. Choose based on your specific requirements for mutability, memory usage, and data type compatibility.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.