# Python's min() and max(): Find Smallest and Largest Values

Python’s built-in `min()` and `max()` functions come in handy when you need to find the smallest and largest values in an iterable or in a series of regular arguments. Even though these might seem like fairly basic computations, they turn out to have many interesting use cases in real-world programing. You’ll try out some of those use cases here.

In this tutorial, you’ll learn how to:

• Use Python’s `min()` and `max()` to find smallest and largest values in your data
• Call `min()` and `max()` with a single iterable or with any number of regular arguments
• Use `min()` and `max()` with strings and dictionaries
• Tweak the behavior of `min()` and `max()` with the `key` and `default` arguments
• Use comprehensions and generator expressions as arguments to `min()` and `max()`

Once you have this knowledge under your belt, then you’ll be prepared to write a bunch of practical examples that will showcase the usefulness of `min()` and `max()`. Finally, you’ll code your own versions of `min()` and `max()` in pure Python, which can help you understand how these functions work internally.

To get the most out of this tutorial, you should have some previous knowledge of Python programming, including topics like `for` loops, functions, list comprehensions, and generator expressions.

## Getting Started With Python’s `min()` and `max()` Functions

Python includes several built-in functions that make your life more pleasant and productive because they mean you don’t need to reinvent the wheel. Two examples of these functions are `min()` and `max()`. They mostly apply to iterables, but you can use them with multiple regular arguments as well. What’s their job? They take care of finding the smallest and largest values in their input data.

Whether you’re using Python’s `min()` or `max()`, you can use the function to achieve two slightly different behaviors. The standard behavior for each is to return the minimum or maximum value through straightforward comparison of the input data as it stands. The alternative behavior is to use a single-argument function to modify the comparison criteria before finding the smallest and largest values.

To explore the standard behavior of `min()` and `max()`, you can start by calling each function with either a single iterable as an argument or with two or more regular arguments. That’s what you’ll do right away.

### Calling `min()` and `max()` With a Single Iterable Argument

The built-in `min()` and `max()` have two different signatures that allow you to call them either with an iterable as their first argument or with two or more regular arguments. The signature that accepts a single iterable argument looks something like this:

``````min(iterable, *[, default, key]) -> minimum_value

max(iterable, *[, default, key]) -> maximum_value
``````

Both functions take a required argument called `iterable` and return the minimum and maximum values respectively. They also take two optional keyword-only arguments: `default` and `key`.

Here’s a summary of what the arguments to `min()` and `max()` do:

Argument Description Required
`iterable` Takes an iterable object, like a list, tuple, dictionary, or string Yes
`default` Holds a value to return if the input iterable is empty No
`key` Accepts a single-argument function to customize the comparison criteria No

Later in this tutorial, you’ll learn more about the optional `default` and `key` arguments. For now, just focus on the `iterable` argument, which is a required argument that leverages the standard behavior of `min()` and `max()` in Python:

>>>
``````>>> min([3, 5, 9, 1, -5])
-5

>>> min([])
Traceback (most recent call last):
...
ValueError: min() arg is an empty sequence

>>> max([3, 5, 9, 1, -5])
9

>>> max([])
Traceback (most recent call last):
...
ValueError: max() arg is an empty sequence
``````

In these examples, you call `min()` and `max()` with a list of integer numbers and then with an empty list. The first call to `min()` returns the smallest number in the input list, `-5`. In contrast, the first call to `max()` returns the largest number in the list, or `9`. If you pass an empty iterator to `min()` or `max()`, then you get a `ValueError` because there’s nothing to do on an empty iterable.

An important detail to note about `min()` and `max()` is that all the values in the input iterable must be comparable. Otherwise, you get an error. For example, numeric values work okay:

>>>
``````>>> min([3, 5.0, 9, 1.0, -5])
-5

>>> max([3, 5.0, 9, 1.0, -5])
9
``````

These examples combine `int` and `float` numbers in the calls to `min()` and `max()`. You get the expected result in both cases because these data types are comparable.

However, what would happen if you mixed strings and numbers? Check out the following examples:

>>>
``````>>> min([3, "5.0", 9, 1.0, "-5"])
Traceback (most recent call last):
...
TypeError: '<' not supported between instances of 'str' and 'int'

>>> max([3, "5.0", 9, 1.0, "-5"])
Traceback (most recent call last):
...
TypeError: '>' not supported between instances of 'str' and 'int'
``````

You can’t call `min()` or `max()` with an iterable of noncomparable types as an argument. In this example, a function tries to compare a number to a string, which is like comparing apples and oranges. The end result it that you get a `TypeError`.

### Calling `min()` and `max()` With Multiple Arguments

The second signature of `min()` and `max()` allows you to call them with any number of arguments, provided that you use at least two arguments. This signature has the following form:

``````min(arg_1, arg_2[, ..., arg_n], *[, key]) -> minimum_value

max(arg_1, arg_2[, ..., arg_n], *[, key]) -> maximum_value
``````

Again, these functions return the minimum and maximum values, respectively. Here’s the meaning of the arguments in the above signature:

Argument Description Required
`arg_1, arg_2, ..., arg_n` Accepts any number of regular arguments to compare Yes (at least two of them)
`key` Takes a single-argument function to customize the comparison criteria No

This variation of `min()` or `max()` doesn’t have a `default` argument. You must provide at least two arguments in the call for the function to work correctly. So, a `default` value isn’t required, because you’ll always have at least two values to compare in order to find the minimum or maximum.

To try out this alternative signature, run the following examples:

>>>
``````>>> min(3, 5, 9, 1, -5)
-5

>>> max(3, 5, 9, 1, -5)
9
``````

You can call `min()` or `max()` with two or more regular arguments. Again, you’ll get the minimum or maximum value in the input data, respectively. The only condition is that the arguments must be comparable.

## Using `min()` and `max()` With Strings and Iterables of Strings

By default, `min()` and `max()` can process values that are comparable. Otherwise, you get a `TypeError`, as you’ve already learned. Up to this point, you’ve seen examples that use numeric values either in an iterable or as multiple regular arguments.

Using `min()` and `max()` with numeric values is arguably the most common and useful use case of these functions. However, you can also use the functions with strings and iterables of strings. In these cases, the alphabetical order of characters will decide the final result.

For example, you can use `min()` and `max()` to find the smallest and largest letters in some text. In this context, smallest means closest to the beginning of the alphabet, and largest means closest to the end of the alphabet:

>>>
``````>>> min("abcdefghijklmnopqrstuvwxyz")
'a'

>>> max("abcdefghijklmnopqrstuvwxyz")
'z'

>>> min("abcdWXYZ")
'W'

>>> max("abcdWXYZ")
'd'
``````

As promised, in the first two examples, `min()` returns `'a'` and `max()` returns `'z'`. However, in the second pair of examples, `min()` returns `'W'` and `max()` returns `'d'`. Why? Because uppercase letters come before lowercase letters in Python’s default character set, UTF-8.

Using `min()` or `max()` with a string as an argument isn’t limited to just letters. You can use strings containing any possible characters in your current character set. For example, if you’re working with the set of ASCII characters only, then the smallest character is the character closest to the beginning of the ASCII table. In contrast, the largest character is the character closest to the end of the table.

With other character sets like UTF-8, `min()` and `max()` behave similarly:

>>>
``````>>> # UTF-8 characters

>>> min("abc123ñ")
'1'

>>> max("abc123ñ")
'ñ'
``````

Behind the scenes, `min()` and `max()` use the character’s numeric value to find the minimum and maximum characters in the input string. For example, in the Unicode character table, the uppercase `A` has a smaller numeric value than the lowercase `a`:

>>>
``````>>> ord("A")
65

>>> ord("a")
97
``````

Python’s built-in `ord()` function takes a single Unicode character and returns an integer representing the Unicode code point of that character. In these examples, the code point for the uppercase `"A"` is lower than the code point for the lowercase `"a"`.

This way, when you call `min()` and `max()` with both letters, you get results that match the order of the underlying Unicode code points of these letters:

>>>
``````>>> min("aA")
'A'

>>> max("aA")
'a'
``````

What makes `"A"` smaller than `"a"`? The quick answer is the letter’s Unicode code point. All characters that you can type on your keyboard, and many other characters, have their own code points in the Unicode table. Python uses these code points to determine the minimum and maximum character when it comes to using `min()` and `max()`.

Finally, you can also call `min()` and `max()` with iterables of strings or with multiple string arguments. Again, both functions will determine their return value by comparing the strings alphabetically:

>>>
``````>>> min(["Hello", "Pythonista", "and", "welcome", "world"])
'Hello'

>>> max(["Hello", "Pythonista", "and", "welcome", "world"])
'world'
``````

To find the smallest or largest string in an iterable of strings, `min()` and `max()` compare all the strings alphabetically based on the code points of initial characters.

In the first example, the uppercase `"H"` comes before `"P"`, `"a"`, and `"w"` in the Unicode table. So, `min()` immediately concludes that `"Hello"` is the smallest string. In the second example, the lowercase `"w"` comes after all the other strings’ initial letters.

Note that there are two words that start with `"w"`, `"welcome"` and `"world"`. So, Python proceeds to look at the second letter of each word. The result is that `max()` returns `"world"` because `"o"` comes after `"e"`.

## Processing Dictionaries With `min()` and `max()`

When it comes to processing Python dictionaries with `min()` and `max()`, you need to consider that if you use the dictionary directly, then both functions will operate on the keys:

>>>
``````>>> prices = {
...    "banana": 1.20,
...    "pineapple": 0.89,
...    "apple": 1.57,
...    "grape": 2.45,
... }

>>> min(prices)
'apple'

>>> max(prices)
'pineapple'
``````

In these examples, `min()` returns the alphabetically smallest key in `prices`, and `max()` returns the largest one. You can get the same result using the `.keys()` method on your input dictionary:

>>>
``````>>> min(prices.keys())
'apple'

>>> max(prices.keys())
'pineapple'
``````

The only difference between this latter example and the previous one is that here, the code is more explicit and clear about what you’re doing. Anyone reading your code will quickly realize that you want to find the smallest and largest keys in the input dictionary.

Another common requirement would be to find the smallest and largest values in a dictionary. To continue with the `prices` example, say you want to know the smallest and largest prices. In this situation, you can use the `.values()` method:

>>>
``````>>> min(prices.values())
0.89

>>> max(prices.values())
2.45
``````

In these examples, `min()` goes through all the values in `prices` and finds the minimum price. Similarly, `max()` iterates over the values of `prices` and returns the maximum price.

Finally, you can also use the `.items()` method on the input dictionary to find the minimum and maximum key-value pairs:

>>>
``````>>> min(prices.items())
('apple', 1.57)

>>> max(prices.items())
('pineapple', 2.45)
``````

In this case, `min()` and `max()` use Python’s internal rules to compare tuples and find the smallest and largest items in the input dictionary.

Python compares tuples item by item. For example, to determine if `(x1, x2)` is greater than `(y1, y2`), Python tests `x1 > y1`. If this condition is `True`, then Python concludes that the first tuple is greater than the second without checking the rest of the items. In contrast, if `x1 < y1`, then Python concludes that the first tuple is less than the second.

Finally, if `x1 == y1`, then Python compares the second pair of items using the same rules. Note that in this context, the first item of each tuple comes from the dictionary keys, and because dictionary keys are unique, the items can’t be equal. So, Python will never have to compare the second values.

## Tweaking the Standard Behavior of `min()` and `max()` With `key` and `default`

Up to this point, you’ve learned how `min()` and `max()` work in their standard form. In this section, you’ll learn how to tweak the standard behavior of both functions by using the `key` and `default` keyword-only arguments.

The `key` argument to `min()` or `max()` allows you to provide a single-argument function that will be applied to every value in the input data. The goal is to modify the comparison criteria to use in finding the minimum or maximum value.

As an example of how this feature can be useful, say that you have a list of numbers as strings, and want to find the smallest and largest numbers. If you process the list directly with `min()` and `max()`, then you get the following results:

>>>
``````>>> min(["20", "3", "35", "7"])
'20'

>>> max(["20", "3", "35", "7"])
'7'
``````

These may not be the results that you need or expect. You’re getting the smallest and largest strings based on Python’s string comparison rules rather than based on the actual numeric value of each string.

In that case, the solution is to pass the built-in `int()` function as the `key` argument to `min()` and `max()`, like in the following examples:

>>>
``````>>> min(["20", "3", "35", "7"], key=int)
'3'

>>> max(["20", "3", "35", "7"], key=int)
'35'
``````

Great! Now the result of `min()` or `max()` depends on the numeric values of the underlying strings. Note that you don’t need to call `int()`. You just pass `int` without the pair of parentheses because `key` expects a function object, or more accurately, a callable object.

The second keyword-only argument that allows you to customize the standard behavior of `min()` or `max()` is `default`. Remember that this argument is only available when you call the function with a single iterable as an argument.

The job of `default` is to provide a suitable default value as the return value of `min()` or `max()` when it’s called with an empty iterable:

>>>
``````>>> min([], default=42)
42

>>> max([], default=42)
42
``````

In these examples, the input iterable is an empty list. The standard behavior is for `min()` or `max()` to raise a `ValueError` complaining about the empty sequence argument. However, because you supply a value to `default`, both functions now return this value instead of raising an exception and breaking your code.

## Using `min()` and `max()` With Comprehensions and Generator Expressions

You can also call `min()` or `max()` with a list comprehension or generator expression as an argument. This feature comes in handy when you need to transform the input data right before finding the minimum or maximum transformed value.

When you feed a list comprehension into `min()` or `max()`, the resulting value will come from the transformed data rather than from the original data:

>>>
``````>>> letters = ["A", "B", "C", "X", "Y", "Z"]

>>> min(letters)
'A'
>>> min([letter.lower() for letter in letters])
'a'

>>> max(letters)
'Z'
>>> max([letter.lower() for letter in letters])
'z'
``````

The second call to `min()` takes a list comprehension as an argument. This comprehension transforms the original data in `letters` by applying the `.lower()` method to each letter. The final result is the lowercase `"a"`, which isn’t present in the original data. Something similar happens with the examples covering `max()`.

Note that using `min()` or `max()` with a list comprehension is similar to using the `key` argument. The main difference is that with comprehensions, the final result is a transformed value, while with `key`, the result comes from the original data:

>>>
``````>>> letters = ["A", "B", "C", "X", "Y", "Z"]

>>> min([letter.lower() for letter in letters])
'a'

>>> min(letters, key=str.lower)
'A'
``````

In both examples, `min()` uses `.lower()` to somehow modify the comparison criteria. The difference is that the comprehension actually transforms the input data before doing the computation, so the resulting value comes from the transformed data rather than from the original.

List comprehensions create a complete list in memory, which is often a wasteful operation. This fact holds especially true if you don’t need the resulting list in your code anymore, which could be the case with `min()` and `max()`. So, it’s always more efficient to use a generator expression instead.

The syntax for generator expressions is almost the same as for list comprehensions:

>>>
``````>>> letters = ["A", "B", "C", "X", "Y", "Z"]

>>> min(letters)
'A'
>>> min(letter.lower() for letter in letters)
'a'

>>> max(letters)
'Z'
>>> max(letter.lower() for letter in letters)
'z'
``````

The main syntax difference is that a generator expression uses parentheses instead of square brackets (`[]`). Because a function call already requires parentheses, you just need to remove the square brackets from your comprehension-based examples, and you’re good to go. Unlike list comprehensions, generator expressions yield items on demand, which makes them memory efficient.

## Putting Python’s `min()` and `max()` Into Action

So far, you’ve learned the basics of using `min()` and `max()` to find the smallest and largest values in an iterable or in a series of individual values. You learned how `min()` and `max()` work with different built-in Python data types, such as numbers, strings, and dictionaries. You also explored how to tweak the standard behavior of these functions and how to use them with list comprehensions and generator expressions.

Now you’re ready to start coding a few practical examples that will show you how to use `min()` and `max()` in your own code.

### Removing the Smallest and Largest Numbers in a List

To kick things off, you’ll start with a short example of how to remove the minimum and maximum values from a list of numbers. To do that, you can call `.remove()` on your input list. Depending on your needs, you’ll use `min()` or `max()` to select the value that you’ll remove from the underlying list:

>>>
``````>>> sample = [4, 5, 7, 6, -12, 4, 42]

>>> sample.remove(min(sample))
>>> sample
[4, 5, 7, 6, 4, 42]

>>> sample.remove(max(sample))
>>> sample
[4, 5, 7, 6, 4]
``````

In these examples, the minimum and maximum values in `sample` could be outlier data points that you want to remove so that they don’t affect your further analysis. Here, `min()` and `max()` provide the arguments to `.remove()`.

### Building Lists of Minimum and Maximum Values

Now say that you have a list of lists representing a matrix of numeric values, and you need to build lists containing the smallest and largest values from every row in the input matrix. To do this, you can use `min()` and `max()` along with a list comprehension:

>>>
``````>>> matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

>>> [min(x) for x in matrix]
[1, 4, 7]

>>> [max(x) for x in matrix]
[3, 6, 9]
``````

The first comprehension iterates over the sublists in `matrix` and uses `min()` to build a list containing the smallest value from each sublist. The second comprehension does a similar task but uses `max()` to create a list containing the largest values from the sublists in `matrix`.

Even though `min()` and `max()` provide a quick way to deal with the examples in this section, the NumPy library is highly recommended when it comes to processing matrixes in Python because NumPy has specific and optimized tools for the job.

### Clipping Values to the Edges of an Interval

Sometimes you have a list of numeric values and want to clip them to the edges or limits of a given interval. For example, if a given value is greater than the interval’s upper limit, then you need to convert it down to that limit. To do this operation, you can use `min()`.

Wait! Why `min()`? You’re dealing with the large values, aren’t you? The point is that you need to compare each large value to the interval’s upper limit and then choose the smaller of the two. You’ll essentially set all large values to a prescribed upper limit:

>>>
``````>>> # Clip values to the largest interval's edge

>>> upper = 100
>>> numbers = [42, 78, 200, -230, 25, 142]

>>> [min(number, upper) for number in numbers]
[42, 78, 100, -230, 25, 100]
``````

The call to `min()` compares every number to the interval’s upper limit. If the target number is greater than the limit, then `min()` returns the limit. The net effect is that all the values that are greater than the limit are now clipped to it. In this example, the numbers `200` and `142` are clipped to `100`, which is the interval’s upper limit.

In contrast, if you want to clip small values to the interval’s lower limit, then you can use `max()`, like in the following example:

>>>
``````>>> # Clip values to the smallest interval's edge

>>> lower = 10
>>> numbers = [42, 78, 200, -230, 25, 142]

>>> [max(number, lower) for number in numbers]
[42, 78, 200, 10, 25, 142]
``````

This call to `max()` clips the small values to the interval’s lower limit. To do this clipping, `max()` compares the current number and the interval’s limit to find the maximum value. In the example, `-230` is the only number that gets clipped.

Finally, you can run both operations in one go by combining `min()` and `max()`. Here’s how to do it:

>>>
``````>>> # Clipping values to 10 - 100

>>> lower, upper = 10, 100
>>> numbers = [42, 78, 100, -230, 25, 142]

>>> [max(min(number, upper), lower) for number in numbers]
[42, 78, 100, 10, 25, 100]
``````

To clip all the values that fall outside the interval’s limits, this comprehension combines `min()` and `max()`. The call to `min()` compares the current value to the interval’s upper limit, while the call to `max()` compares the result to the lower limit. The final result is that values lower than or greater than the corresponding limit are clipped to the limit itself.

This comprehension works similarly to the `clip()` function from NumPy, which takes an array and the limits of the target interval, then it clips all values outside the interval to the interval’s edges.

### Finding the Closest Points

Now say that you have a list of tuples containing pairs of values that represent Cartesian points. You want to process all these pairs of points and find out which pair has the smallest distance between points. In this situation, you can do something like the following:

>>>
``````>>> import math

>>> point_pairs = [
...     ((12, 5), (9, 4)),
...     ((2, 5), (3, 7)),
...     ((4, 11), (15, 2))
... ]

>>> min(point_pairs, key=lambda points: math.dist(*points))
((2, 5), (3, 7))
``````

In this example, you first import `math` to get access to `dist()`. This function returns the Euclidean distance between two points, p and q, each given as a sequence of coordinates. The two points must have the same number of dimensions.

The `min()` function works its magic through its `key` argument. In this example, `key` takes a `lambda` function that computes the distance between two points. This function becomes the comparison criteria for `min()` to find the pair of points with the minimal distance between points.

In this example, you need a `lambda` function because `key` expects a single-argument function, while `math.dist()` requires two arguments. So, the `lambda` function takes a single argument, `points`, and then unpacks it into two arguments to feed into `math.dist()`.

### Identifying Cheap and Expensive Products

Now say you have a dictionary with the names and prices of several products, and you want to identify the cheapest and most expensive products. In this situation, you can use `.items()` and an appropriate `lambda` function as the `key` argument:

>>>
``````>>> prices = {
...    "banana": 1.20,
...    "pineapple": 0.89,
...    "apple": 1.57,
...    "grape": 2.45,
... }

>>> min(prices.items(), key=lambda item: item)
('pineapple', 0.89)

>>> max(prices.items(), key=lambda item: item)
('grape', 2.45)
``````

In this example, the `lambda` function takes a key-value pair as an argument and returns the corresponding value so that `min()` and `max()` have proper comparison criteria. As a result, you get a tuple with the cheapest and most expensive products in the input data.

### Finding Coprime Integer Numbers

Another interesting example of using `min()` to solve a real-world problem is when you need to figure out if two numbers are coprime. In other words, you need to know if your numbers’ only common divisor is `1`.

In that situation, you can code a Boolean-valued or predicate function like the following:

>>>
``````>>> def are_coprime(a, b):
...     for i in range(2, min(a, b) + 1):
...         if a % i == 0 and b % i == 0:
...             return False
...     return True
...

>>> are_coprime(2, 3)
True
>>> are_coprime(2, 4)
False
``````

In this code snippet, you define `are_coprime()` as a predicate function that returns `True` if the input numbers are coprime. If the numbers aren’t coprime, then the function returns `False`.

The function’s main component is a `for` loop that iterates over a `range` of values. To set the upper limit for this `range` object, you use `min()` with the input numbers as arguments. Again, you’re using `min()` to set the upper limit of some interval.

### Timing Different Implementations of Your Code

You can also use `min()` to compare several of your algorithms, evaluate their execution times, and determine which algorithm is the most efficient. The example below uses `timeit.repeat()` to measure the execution times for two different ways of building a list containing the square values of the numbers from `0` to `99`:

>>>
``````>>> import timeit

>>> min(
...     timeit.repeat(
...         stmt="[i ** 2 for i in range(100)]",
...         number=1000,
...         repeat=3
...     )
... )
0.022141209003166296

>>> min(
...     timeit.repeat(
...         stmt="list(map(lambda i: i ** 2, range(100)))",
...         number=1000,
...         repeat=3
...     )
... )
0.023857666994445026
``````

The call to `timeit.repeat()` runs a string-based statement a given number of times. In these examples, the statement is repeated three times. The call to `min()` returns the smallest execution time from the three repetitions.

By combining `min()`, `repeat()`, and other Python timer functions, you can get an idea of which of your algorithms is most efficient in terms of execution time. The example above shows that list comprehensions can be a little bit faster than the built-in `map()` function when it comes to building new lists.

## Exploring the Role of `.__lt__()` and `.__gt__()` in `min()` and `max()`

As you’ve learned so far, the built-in `min()` and `max()` functions are versatile enough to work with values of various data types, such as numbers and strings. The secret behind this flexibility is that `min()` and `max()` embrace Python’s duck typing philosophy by relying on the `.__lt__()` and `.__gt__()` special methods.

These methods are part of what Python calls rich comparison methods. Specifically, `.__lt__()` and `.__gt__()` support the less than (`<`) and greater than (`>`) operators, respectively. What’s the meaning of support here? When Python finds something like `x < y` in your code, it internally does `x.__lt__(y)`.

The takeaway is that you can use `min()` and `max()` with values of any data type that implements `.__lt__()` and `.__gt__()`. That’s why these functions work with values of all Python’s built-in data types:

>>>
``````>>> "__lt__" in dir(int) and "__gt__" in dir(int)
True

>>> "__lt__" in dir(float) and "__gt__" in dir(float)
True

>>> "__lt__" in dir(str) and "__gt__" in dir(str)
True

>>> "__lt__" in dir(list) and "__gt__" in dir(list)
True

>>> "__lt__" in dir(tuple) and "__gt__" in dir(tuple)
True

>>> "__lt__" in dir(dict) and "__gt__" in dir(dict)
True
``````

Python’s built-in data types implement the `.__lt__()` and `.__gt__()` special methods. So, you can feed any of these data types into `min()` and `max()`, with the only condition being that the involved data types are comparable.

You can also make instances of your custom classes compatible with `min()` and `max()`. To achieve this, you need to provide your own implementations of `.__lt__()` and `.__gt__()`. Consider the following `Person` class as an example of this compatibility:

``````# person.py

from datetime import date

class Person:
def __init__(self, name, birth_date):
self.name = name
self.birth_date = date.fromisoformat(birth_date)

def __repr__(self):
return (
f"{type(self).__name__}"
f"({self.name}, {self.birth_date.isoformat()})"
)

def __lt__(self, other):
return self.birth_date > other.birth_date

def __gt__(self, other):
return self.birth_date < other.birth_date
``````

Note that the implementation of `.__lt__()` and `.__gt__()` requires an argument that’s typically named `other`. This argument represents the second operand in the underlying comparison operations. For example, in an expression like `x < y`, you’ll have that `x` will be `self` and `y` will be `other`.

In this example, `.__lt__()` and `.__gt__()` return the result of comparing two people’s `.birth_date` attributes. Here’s how this works in practice:

>>>
``````>>> from person import Person

>>> jane = Person("Jane Doe", "2004-08-15")
>>> john = Person("John Doe", "2001-02-07")

>>> jane < john
True
>>> jane > john
False

>>> min(jane, john)
Person(Jane Doe, 2004-08-15)

>>> max(jane, john)
Person(John Doe, 2001-02-07)
``````

Cool! You can process `Person` objects with `min()` and `max()` because the class provides implementation of `.__lt__()` and `.__gt__()`. The call to `min()` returns the youngest person, and the call to `max()` returns the oldest.

Note that if a given custom class doesn’t provide these methods, then its instances won’t support `min()` and `max()` operations:

>>>
``````>>> class Number:
...     def __init__(self, value):
...         self.value = value
...

>>> x = Number(21)
>>> y = Number(42)

>>> min(x, y)
Traceback (most recent call last):
...
TypeError: '<' not supported between instances of 'Number' and 'Number'

>>> max(x, y)
Traceback (most recent call last):
...
TypeError: '>' not supported between instances of 'Number' and 'Number'
``````

Because this `Number` class doesn’t provide suitable implementations of `.__lt__()` and `.__gt__()`, `min()` and `max()` respond with a `TypeError`. The error message tells you that the comparison operations aren’t supported in your current class.

## Emulating Python’s `min()` and `max()`

Up to this point, you’ve learned how Python’s `min()` and `max()` functions work. You’ve used them to find the smallest and largest values among several numbers, strings, and more. You know how to call these functions either with a single iterable as an argument or with an undefined number of regular arguments. Finally, you’ve coded a series of practical examples that approach real-world problems using `min()` and `max()`.

Although Python kindly provides you with `min()` and `max()` to find the smallest and largest values in your data, learning how to do this computation from scratch is a helpful exercise that can improve your logical thinking and your programming skills.

In this section, you’ll learn how to find minimum and maximum values in your data. You’ll also learn how to implement your own versions of `min()` and `max()`.

### Understanding the Code Behind `min()` and `max()`

To find the minimum value in a small list of numbers as a human, you’d normally check the numbers and implicitly compare all of them in your mind. Yes, your brain is amazing! However, computers aren’t that smart. They need detailed instructions to accomplish any task.

You’ll have to tell your computer to iterate over all the values while comparing them in pairs. In the process, the computer has to take note of the current minimum value in each pair until the list of values is processed entirely.

This explanation may be hard to visualize, so here’s a Python function that does the work:

>>>
``````>>> def find_min(iterable):
...     minimum = iterable
...     for value in iterable[1:]:
...         if value < minimum:
...             minimum = value
...     return minimum
...

>>> find_min([2, 5, 3, 1, 9, 7])
1
``````

In this code snippet, you define `find_min()`. This function assumes that `iterable` isn’t empty and that its values are in an arbitrary order.

The function treats the first value as a tentative `minimum`. Then the `for` loop iterates over the rest of the elements in the input data.

The conditional statement compares the current `value` to the tentative `minimum` in the first iteration. If the current `value` is smaller than `minimum`, then the conditional updates `minimum` accordingly.

Each new iteration compares the current `value` to the updated `minimum`. When the function reaches the end of `iterable`, `minimum` will hold the smallest value in the input data.

Cool! You’ve coded a function that finds the smallest value in an iterable of numbers. Now revisit `find_min()` and think of how you’d code a function to find the largest value. Yes, that’s it! You just have to change the comparison operator from less than (`<`) to greater than (`>`), and probably rename the function and some local variables to prevent confusion.

Your new function can look something like this:

>>>
``````>>> def find_max(iterable):
...     maximum = iterable
...     for value in iterable[1:]:
...         if value > maximum:
...             maximum = value
...     return maximum
...

>>> find_max([2, 5, 3, 1, 9, 7])
9
``````

Note that `find_max()` shares most of its code with `find_min()`. The most important difference, apart from naming, is that `find_max()` uses the greater than operator (`>`) instead of the less than operator (`<`).

As an exercise, you can think of how to avoid repetitive code in `find_min()` and `find_max()` following the DRY (don’t repeat yourself) principle. This way, you’ll be ready to emulate the complete behavior of `min()` and `max()` using your Python skills, which you’ll tackle in just a moment.

Before diving in, you need to be aware of the knowledge requirements. You’ll be combining topics like conditional statements, exception handling, list comprehensions, definite iteration with `for` loops, and `*args` and optional arguments in functions.

If you feel that you don’t know everything about these topics, then don’t worry. You’ll learn by doing. If you get stuck, then you can go back and review the linked resources.

### Planning Your Custom `min()` and `max()` Versions

To write your custom implementations of `min()` and `max()`, you’ll start by coding a helper function that’s able to find the smallest or largest value in the input data, depending on the arguments you use in the call. Of course, the helper function will especially depend on the operator used for comparing the input values.

Your helper function will have the following signature:

``````min_max(*args, operator, key=None, default=None) -> extreme_value
``````

Here’s what each argument does:

Argument Description Required
`*args` Allows you to call the function with either an iterable or any number of regular arguments Yes
`operator` Holds the appropriate comparison operator function for the computation at hand Yes
`key` Takes a single-argument function that modifies the function’s comparison criteria and behavior No
`default` Stores a default value to return when you call the function with an empty iterable No

The body of `min_max()` will start by processing `*args` to build a list of values. Having a standardized list of values will allow you to write the required algorithm to find the minimum and maximum values in the input data.

Then the function needs to deal with the `key` and `default` arguments before computing the minimum and maximum, which is the final step inside `min_max()`.

With `min_max()` in place, the final step is to define two independent functions on top of it. These functions will use appropriate comparison operator functions to find the minimum and maximum values, respectively. You’ll learn more about operator functions in a moment.

### Standardizing the Input Data From `*args`

To standardize the input data, you need to check if the user is providing a single iterable or any number of regular arguments. Fire up your favorite code editor or IDE and create a new Python file called `min_max.py`. Then add the following piece of code to it:

``````# min_max.py

def min_max(*args, operator, key=None, default=None):
if len(args) == 1:
try:
values = list(args)  # Also check if the object is iterable
except TypeError:
raise TypeError(
f"{type(args).__name__} object is not iterable"
) from None
else:
values = args
``````

Here, you define `min_max()`. The function’s first portion standardizes the input data for further processing. Because the user will be able to call `min_max()` with either a single iterable or with several regular arguments, you need to check the length of `args`. To do this check, you use the built-in `len()` function.

If `args` holds only one value, then you need to check if that argument is an iterable object. You use `list()`, which implicitly does the check and also turns the input iterable into a list.

If `list()` raises a `TypeError`, then you catch it and raise your own `TypeError` to inform the user that the provided object isn’t iterable, just like `min()` and `max()` do in their standard form. Note that you use the `from None` syntax to hide away the traceback of the original `TypeError`.

The `else` branch runs when `args` holds more than one value, which handles the cases where the user calls the function with several regular arguments instead of with a single iterable of values.

If this conditional doesn’t ultimately raise a `TypeError`, then `values` will hold a list of values that may be empty. Even if the resulting list is empty, it’s now clean and ready for continuing the process of finding its minimum or maximum value.

### Processing the `default` Argument

To continue writing `min_max()`, you can now process the `default` argument. Go ahead and add the following code to the end of the function:

``````# min_max.py
# ...

def min_max(*args, operator, key=None, default=None):
# ...

if not values:
if default is None:
raise ValueError("args is an empty sequence")
return default
``````

In this code snippet, you define a conditional to check if `values` holds an empty list. If that’s the case, then you check the `default` argument to see if the user provided a value for it. If `default` is still `None`, then a `ValueError` is raised. Otherwise, `default` gets returned. This behavior emulates the standard behavior of `min()` and `max()` when you call them with empty iterables.

### Handling the Optional `key` Function

Now you need to process the `key` argument and prepare the data for finding the smallest and largest values according to the provided `key`. Go ahead and update `min_max()` with the following code:

``````# min_max.py
# ...

def min_max(*args, operator, key=None, default=None):
# ...

if key is None:
keys = values
else:
if callable(key):
keys = [key(value) for value in values]
else:
raise TypeError(f"{type(key).__name__} object is not a callable")
``````

You start this code fragment with a conditional that checks if the user hasn’t provided a `key` function. If they haven’t, then you create a list of keys directly from your original `values`. You’ll use these keys as comparison keys in computing the minimum and maximum.

On the other hand, if the user has provided a `key` argument, then you need to make sure that the argument is actually a function or callable object. To do this, you use the built-in `callable()` function, which returns `True` if its argument is a callable and `False` otherwise.

Once you’re sure that `key` is a callable object, then you build the list of comparison keys by applying `key` to each value in the input data.

Finally, if `key` isn’t a callable object, then the `else` clause runs, raising a `TypeError`, just like `min()` and `max()` do in a similar situation.

### Finding Minimum and Maximum Values

The last step to finish your `min_max()` function is to find the minimum and maximum values in the input data, just like `min()` and `max()` do. Go ahead and wrap up `min_max()` with the following code:

``````# min_max.py
# ...

def min_max(*args, operator, key=None, default=None):
# ...

extreme_key, extreme_value = keys, values
for key, value in zip(keys[1:], values[1:]):
if operator(key, extreme_key):
extreme_key = key
extreme_value = value
return extreme_value
``````

You set the `extreme_key` and `extreme_value` variables to the first value in `keys` and in `values`, respectively. These variables will provide the initial key and value for computing the minimum and maximum.

Then you loop over the remaining keys and values in one go using the built-in `zip()` function. This function will yield key-value tuples by combining the values in your `keys` and `values` lists.

The conditional inside the loop calls `operator` to compare the current `key` to the tentative minimum or maximum key stored in `extreme_key`. At this point, the `operator` argument will hold either `lt()` or `gt()` from the `operator` module, depending on if you want to find the minimum or maximum value, respectively.

For example, when you want to find the smallest value in the input data, `operator` will hold the `lt()` function. When you want to find the largest value, `operator` will hold `gt()`.

Every loop iteration compares the current `key` to the tentative minimum or maximum key and updates the values of `extreme_key` and `extreme_value` accordingly. At the end of the loop, these variables will hold the minimum or maximum key and its corresponding value. Finally, you just need to return the value in `extreme_value`.

### Coding Your Custom `min()` and `max()` Functions

With the `min_max()` helper function in place, you can define your custom versions of `min()` and `max()`. Go ahead and add the following functions to the end of your `min_max.py` file:

``````# min_max.py

from operator import gt, lt

# ...

def custom_min(*args, key=None, default=None):
return min_max(*args, operator=lt, key=key, default=default)

def custom_max(*args, key=None, default=None):
return min_max(*args, operator=gt, key=key, default=default)
``````

In this code snippet, you first import `gt()` and `lt()` from the `operator` module. These functions are the functional equivalent of the greater than (`>`) and less than (`<`) operators, respectively. For example, the Boolean expression `x < y` is equivalent to the function call `lt(x, y)`. You’ll use these functions to provide the `operator` argument to your `min_max()`.

Just like `min()` and `max()`, `custom_min()` and `custom_max()` take `*args`, `key`, and `default` as arguments and return the minimum and maximum values, respectively. To perform the computation, these functions call `min_max()` with the required arguments and with the appropriate comparison `operator` function.

In `custom_min()`, you use `lt()` to find the smallest value in the input data. In `custom_max()`, you use `gt()` to get the largest value.

Click the collapsible section below if you want to get the entire content of your `min_max.py` file:

``````# min_max.py

from operator import gt, lt

def min_max(*args, operator, key=None, default=None):
if len(args) == 1:
try:
values = list(args)  # Also check if the object is iterable
except TypeError:
raise TypeError(
f"{type(args).__name__} object is not iterable"
) from None
else:
values = args

if not values:
if default is None:
raise ValueError("args is an empty sequence")
return default

if key is None:
keys = values
else:
if callable(key):
keys = [key(value) for value in values]
else:
raise TypeError(f"{type(key).__name__} object is not a callable")

extreme_key, extreme_value = keys, values
for key, value in zip(keys[1:], values[1:]):
if operator(key, extreme_key):
extreme_key = key
extreme_value = value
return extreme_value

def custom_min(*args, key=None, default=None):
return min_max(*args, operator=lt, key=key, default=default)

def custom_max(*args, key=None, default=None):
return min_max(*args, operator=gt, key=key, default=default)
``````

Cool! You’ve finished coding your own versions of `min()` and `max()` in Python. Now go ahead and give them a try!

## Conclusion

Now you know how to use Python’s built-in `min()` and `max()` functions to find the smallest and largest values in an iterable or in a series of two or more regular arguments. You also learned about a few other characteristics of `min()` and `max()` that can make them useful in your day-to-day programming.

In this tutorial, you learned how to:

• Find the smallest and largest values using Python’s `min()` and `max()`, respectively
• Call `min()` and `max()` with a single iterable and with several regular arguments
• Use `min()` and `max()` with strings and dictionaries
• Customize the behavior of `min()` and `max()` with `key` and `default`
• Feed comprehensions and generator expressions into `min()` and `max()`

Additionally, you’ve coded a handful of practical examples using `min()` and `max()` to approach real-world problems that you might run into while coding. You’ve also a written custom version of `min()` and `max()` in pure Python, a nice learning exercise that helped you understand the logic behind these built-in functions.

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team. About Leodanis Pozo Ramos

Leodanis is an industrial engineer who loves Python and software development. He's a self-taught Python developer with 6+ years of experience. He's an avid technical writer with a growing number of articles published on Real Python and other sites.

» More about Leodanis

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Master Real-World Python Skills With Unlimited Access to Real Python Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

What Do You Think?