Find the Smallest and Largest Number in a Python List Without Built-ins
Python ships with min() and max(), so why would anyone write this manually? In interviews, the built-ins are often off-limits. More importantly, working through the logic yourself builds the habit of thinking about initialisation, edge cases, and single-pass efficiency — skills that carry over to much harder problems.
The Built-in Baseline
Start here so you know what you are replicating:
numbers = [5, 2, 9, 1, 7, 4]
smallest = min(numbers)largest = max(numbers)
print(f"Smallest: {smallest}, Largest: {largest}")# Output: Smallest: 1, Largest: 9min() and max() each do one full pass — so calling both costs two passes. The manual version below does it in one.
Single-Pass Manual Algorithm
Initialise both smallest and largest to the first element, then walk through the rest of the list once, updating each as needed.
def find_min_max(numbers): """Return (smallest, largest) in one pass. Returns None for empty input.""" if not numbers: return None, None
smallest = numbers[0] largest = numbers[0]
for num in numbers[1:]: # skip the first — already used for init if num < smallest: smallest = num elif num > largest: largest = num
return smallest, largest
data = [5, 2, 9, 1, 7, 4]small, large = find_min_max(data)print(f"Smallest: {small}, Largest: {large}")# Output: Smallest: 1, Largest: 9Time complexity: O(n) — one pass through the list. Space complexity: O(1).
Note the elif: when a number is less than smallest, it cannot also be greater than largest, so skipping the second check saves a comparison on every smaller value.
Handling Edge Cases
# Empty listprint(find_min_max([]))# Output: (None, None)
# Single elementprint(find_min_max([42]))# Output: (42, 42)
# All identical elementsprint(find_min_max([7, 7, 7, 7]))# Output: (7, 7)
# Negative numbersprint(find_min_max([-3, -10, -1, -7]))# Output: (-10, -3)Initialising with float('inf') and float('-inf') is another common pattern, but using numbers[0] avoids sentinel values and handles the single-element case naturally.
Using Python’s Built-ins Correctly in One Call
If you just need both values and built-ins are allowed:
numbers = [5, 2, 9, 1, 7, 4]
# Still two passes, but very readablesmallest, largest = min(numbers), max(numbers)
# Or use min/max with a generator for large iterablesimport randombig_list = [random.randint(1, 10_000) for _ in range(1_000_000)]small, large = min(big_list), max(big_list)Common Mistake: Wrong Initialisation
A frequent error is initialising smallest to 0:
# BUG: if all numbers are positive, 0 is never updatedsmallest = 0for num in [5, 2, 9, 1]: if num < smallest: smallest = numprint(smallest) # 0 — wrong! should be 1Always initialise to the first element, not to an arbitrary constant. The only safe arbitrary sentinel for minimum-finding is float('inf') and for maximum-finding is float('-inf').
def find_min_safe(numbers): smallest = float('inf') for num in numbers: if num < smallest: smallest = num return smallest if smallest != float('inf') else NoneThe single-pass approach using numbers[0] avoids this class of bug altogether and reads more naturally.