Back to Blog
Python

Python min() and max() Functions: Syntax, key, and default

Understand Python's built-in min() and max() functions, including syntax, the key parameter, a default for empty iterables, performance, and edge cases.

python built-insmin functionmax functionkey parameteriterables
Illustration of Python min and max functions comparing values with a key parameter

Python's built-in min() and max() functions are the simplest way to find the smallest and largest items in an iterable or among several arguments. Despite their simplicity, they have subtleties around the key parameter, empty iterables, and mixed-type comparisons that can trip up even experienced developers.

The Core Behavior of min() and max()

Both functions share the same two calling forms:

min(iterable, *, key=None, default=None) min(arg1, arg2, *args, key=None) max(iterable, *, key=None, default=None) max(arg1, arg2, *args, key=None)

The * in the signatures means key and default must be passed as keyword arguments. In the single-iterable form, min() returns the smallest element and max() returns the largest element. In the multiple-argument form, they compare those arguments directly.

numbers = [4, 2, 9, 1] print(min(numbers)) # 1 print(max(numbers)) # 9 print(min(4, 2, 9, 1)) # 1 print(max(4, 2, 9, 1)) # 9

min() uses less-than comparisons to decide whether a candidate should replace the current minimum; max() uses greater-than comparisons. The elements must therefore support ordering. Custom classes can implement __lt__ or __gt__, or you can provide a key function to avoid requiring comparison support on the objects themselves.

Using the key Parameter

The key parameter accepts a callable that transforms each element before comparison. This is useful when you need to compare by a specific attribute or computed value.

words = ["apple", "banana", "cherry", "date"] print(min(words, key=len)) # "date" (shortest) print(max(words, key=len)) # "banana" (longest)

The key function is called exactly once per element, and the returned values are used for comparison. This avoids repeated attribute lookups and keeps the logic in one place.

For dictionaries, you often want to find the key with the maximum value:

prices = {"apple": 1.2, "banana": 0.8, "cherry": 2.5} print(max(prices, key=prices.get)) # "cherry"

Note that max returns the key itself, not the value. If you need the value, use prices[max(prices, key=prices.get)].

Providing a Default for Empty Iterables

By default, calling min() or max() on an empty iterable raises a ValueError. To avoid that, you can supply a default argument, which is returned when the iterable is empty.

empty_list = [] print(min(empty_list, default=0)) # 0 print(max(empty_list, default=0)) # 0

The default is only used when the iterable is empty. It does not affect the comparison when elements exist. It also works only in the single-iterable calling form; min(1, 2, default=0) is invalid. This is especially useful when processing user input or external data that may be empty.

Comparing Multiple Arguments vs. an Iterable

The two calling conventions have a subtle difference. When you pass a single list, min() iterates over it. When you pass multiple arguments, they are treated as the sequence. This matters when you have a list and want to treat it as a single element.

data = [1, 2, 3] print(min(data)) # 1 print(min([data], key=sum)) # [1, 2, 3] because it is the only element

The same distinction applies when building argument lists dynamically: min(items) and min(*items) are not the same.

Performance and Memory Considerations

Both min() and max() iterate through the input once, giving O(n) time complexity. They do not create a sorted copy, so memory usage is O(1) beyond the input. This makes them more efficient than sorting the entire collection when you only need the extreme value.

# Sorting to find min and max is wasteful: sorted(numbers)[0] # O(n log n) min(numbers) # O(n)

An expensive key function still runs once per input element, so it adds directly to the total cost. If you process the same data repeatedly, precompute the key values.

Common Edge Cases and Pitfalls

Mixed-type comparisons raise TypeError in Python 3 because strings and numbers cannot be ordered. This is a common source of errors when data comes from inconsistent sources.

mixed = [1, "2", 3] # min(mixed) # TypeError: '<' not supported between instances of 'str' and 'int'

Another edge case is floating-point NaN. Because NaN comparisons are always false, min() and max() can return NaN if it is the first element, and this NaN then poisons the result because no later comparison can replace it. If NaN appears later, it is usually skipped for the same reason.

values = [float('nan'), 1, 2] print(min(values)) # nan print(max(values)) # nan values2 = [1, 2, float('nan')] print(min(values2)) # 1 print(max(values2)) # 2

If you need to ignore NaN, filter it out before calling min() or max():

import math clean = [x for x in values if not math.isnan(x)] print(min(clean)) # 1 print(max(clean)) # 2

When to Use min() and max() vs. Sorting

If you only need the smallest or largest element, min() and max() are the right tools. If you need the top N elements, heapq.nsmallest() and heapq.nlargest() are more efficient than sorting when N is small relative to the collection size. For the full sorted order, use sorted().

import heapq data = [5, 3, 8, 1, 9, 2] print(heapq.nsmallest(3, data)) # [1, 2, 3] print(heapq.nlargest(3, data)) # [9, 8, 5]

The choice depends on how much of the ordering you actually need. Using min() and max() for a single extreme value avoids unnecessary work.

Python min() and max() Functions: Syntax, key, default, and edge cases | RYUSLOG DEV