A recipe is an algorithm: a fixed list of steps that turns inputs (ingredients) into an output (a dish). Learning algorithmic thinking follows the same path as learning to cook. You follow the recipe exactly, you work out why each step is there, and only then do you start changing things, one change at a time, tasting after each.
This post walks through that path with one small problem, finding the largest number in a list, and then shows where the cooking comparison stops being useful.
Where the comparison holds
| In the kitchen | In code |
|---|---|
| Ingredients | Input (the function’s arguments) |
| The finished dish | Output (the return value) |
| Steps, in order | Statements, run top to bottom |
| “Stir until thick” | A loop with a stopping condition |
| “If the sauce is too thin, simmer longer” | An if statement |
| A technique you reuse (making a roux) | A function |
| Mise en place: chop everything before you start | Reading the problem, listing inputs and edge cases before typing |
| Tasting while you cook | Running tests and printing intermediate values |
| Cooking for 2 versus for 200 | Time complexity: how the work grows with the input |
None of this is deep. It is useful because it describes how people actually learn both skills, and it points at the stage where most beginners get stuck: they want to experiment before they can follow the recipe.
Stage 1: follow the recipe exactly
A first-time cook who “improves” a recipe halfway through usually ends up with something inedible and no idea which change caused it. The coding version is copying a solution, changing a few lines you do not understand, and then being unable to say why it fails.
Here is the recipe for the largest number in a list, written as plain steps:
- Take the first number and call it best so far.
- Look at each remaining number. If it is bigger than best so far, it becomes the new best so far.
- When the numbers run out, best so far is the answer.
And the same recipe in Python:
def find_max(numbers):
best = numbers[0]
for n in numbers[1:]:
if n > best:
best = n
return best
print(find_max([3, 9, 2, 9, 4])) # 9
print(find_max([-7, -3, -12])) # -3
print(find_max([5])) # 5
Follow it by hand once with [3, 9, 2, 9, 4]: best starts at 3, becomes 9 at the second number, stays 9 for the rest. Doing this on paper is the equivalent of cooking along with the recipe open on the counter. Our post on dry-running code with sample test cases covers the habit in more detail.
Stage 2: find out why each step is there
Good cooks know why you salt the pasta water and why the pan must be hot before the onions go in. For the recipe above, the questions are just as concrete.
Why start from the first number and not from 0? Try it:
def wrong_max(numbers):
best = 0
for n in numbers:
if n > best:
best = n
return best
print(wrong_max([-7, -3, -12])) # 0, which is not in the list
Starting at 0 only works if the list contains something at least 0. Starting from an actual element works for every non-empty list. That one detail is the difference between a recipe that works on your kitchen’s stove and one that works on any stove.
What happens with an empty list? find_max([]) raises IndexError: list index out of range, because there is no first element to take. The recipe silently assumed at least one ingredient. Decide what you want: raise a clearer error, or return None. Either is fine as long as you chose it on purpose.
What happens with ties? In [3, 9, 2, 9, 4] the 9 appears twice. The > comparison keeps the first one. Using >= would keep the last one. For plain numbers it makes no difference, but if you were returning the position it would.
Stage 3: change one thing at a time
This is the experimenting part, and the rule is the same as in the kitchen: if you change salt, spice and cooking time together, you learn nothing. Change one step, run it, compare.
Experiment 1: return the position as well as the value.
def find_max_with_index(numbers):
if not numbers:
raise ValueError("find_max_with_index needs at least one number")
best_i = 0
for i in range(1, len(numbers)):
if numbers[i] > numbers[best_i]:
best_i = i
return numbers[best_i], best_i
print(find_max_with_index([3, 9, 2, 9, 4])) # (9, 1)
Experiment 2: flip the comparison to get the minimum. Experiment 3: keep the two largest values instead of one. Each is a small variation, and each teaches you what a line of the original was doing. Also try replacing the whole loop with Python’s built-in max(). It is shorter and you should use it in real code, but notice that it does the same three steps inside. In an interview, an interviewer who asks you to find the maximum wants to see the loop, not the call.
When you invent a variation yourself, like “largest number that is also even”, you are no longer cooking from a recipe. You are writing one. Our post on breaking complex problems into steps is about that move.
Stage 4: cook for a crowd
A recipe for four people does not simply multiply by fifty. Pots, oven space and timing all change. In code, the question is how the work grows as the input grows.
Searching for a value in a sorted list shows this well. Checking every item takes work proportional to the list length. Binary search, which repeatedly looks at the middle and discards half, takes work proportional to the logarithm of the length. Counting the steps on a list of one million even numbers (0, 2, 4, up to 1,999,998):
def linear_search(items, target):
steps = 0
for x in items:
steps += 1
if x == target:
return True, steps
return False, steps
def binary_search(items, target):
lo, hi, steps = 0, len(items) - 1, 0
while lo <= hi:
steps += 1
mid = (lo + hi) // 2
if items[mid] == target:
return True, steps
if items[mid] < target:
lo = mid + 1
else:
hi = mid - 1
return False, steps
data = list(range(0, 2_000_000, 2))
print(linear_search(data, 1_999_998)) # (True, 1000000)
print(binary_search(data, 1_999_998)) # (True, 20)
One million steps against twenty. Binary search only works because the list is sorted, which is the algorithmic version of “this technique needs the dough to rest first”. If you want to go deeper, read linear vs binary search or time complexity explained.
Where the comparison breaks
- A computer has no taste. A cook adjusts to what they see and smell. Code does exactly what you wrote, including the mistakes, so you must decide in advance what “done” means and test for it.
- Recipes tolerate vagueness; algorithms do not. “A pinch of salt” is fine. “Loop until it looks sorted” is not a step.
- Correctness is checkable. A dish is a matter of taste. An algorithm can be proven or tested against every input you can think of, and it should be.
- The same input always gives the same output. Your oven has hot spots; a deterministic function does not.
Use the analogy to get started and drop it when it stops helping.
How to practice in this order
- Pick a small problem and solve it by following a worked solution, typing it yourself instead of pasting.
- Close the solution and write it again from memory. Where you get stuck is what you did not understand.
- Break it on purpose with an empty input, a single item, duplicates, negative numbers. Fix what breaks.
- Change one requirement and adapt the code (smallest instead of largest, position instead of value).
- Only then try problems you have never seen.
If you want guided problems where each step is small and your code is checked immediately, the Python course at AlgoCademy works this way. A good first one is Maximum Number In Array, the same problem as above.
