Technology  /  Python

🐍 Python 78 guides · updated 2026

From first variable to OOP, generators, and real projects — the language that runs everything from data pipelines to AI agents, taught the practical way.

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: 9

min() 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: 9

Time 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 list
print(find_min_max([]))
# Output: (None, None)
# Single element
print(find_min_max([42]))
# Output: (42, 42)
# All identical elements
print(find_min_max([7, 7, 7, 7]))
# Output: (7, 7)
# Negative numbers
print(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 readable
smallest, largest = min(numbers), max(numbers)
# Or use min/max with a generator for large iterables
import random
big_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 updated
smallest = 0
for num in [5, 2, 9, 1]:
if num < smallest:
smallest = num
print(smallest) # 0 — wrong! should be 1

Always 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 None

The single-pass approach using numbers[0] avoids this class of bug altogether and reads more naturally.