Imperative
Tell the machine how, step by step
———
The exact same 4-function calculator, written three ways.
Run all three in your browser — the page executes the actual
calculator.py files from
the repo
with a real Python interpreter (WebAssembly, no server involved).
Pick an operation, hit Run all three, and watch what each paradigm does differently with the very same numbers. Run a few in a row and watch the state row — that’s where the paradigms really diverge.
Tell the machine how, step by step
———Describe what with pure functions
———Bundle data + behaviour into objects
———| Imperative | Functional | Object-Oriented | |
|---|---|---|---|
| Core idea | Do this, then this, then this | Compute values from values | Objects own data + behaviour |
| Dispatch | if/elif on a string | Dict of functions (operators['1']) | Method call on an object |
| History | None — result is forgotten | New tuple returned; old one untouched | Object mutates self.history |
| Loop | while True | Recursion (repl calls itself) | while True |
| Watch out for | Logic and I/O get tangled as it grows | Recursion depth; takes a mindset shift | Shared mutable state; boilerplate for tiny programs |
A kata is a small exercise you repeat until the pattern sticks. Clone the repo, work in a branch, and use the playground above to check your understanding.
Pick the paradigm you know least. Without looking at the repo version,
write the calculator from scratch: menu loop, four operations,
Cannot divide by zero, q to quit, Goodbye at the end.
Then diff your version against the repo’s. Where did you improvise? Where did the paradigm force your hand?
The spec fits in 30–90 lines in all three styles. If your functional version uses a while loop or your OO version has no class, start over — that’s the point of the exercise.
Add a fifth operation, power(x, y) = x ** y, to all three versions.
Count how many places you had to touch in each file.
What to notice: functional needs a new def plus one dict entry;
imperative needs a def plus another elif branch;
OO needs one method (and a dispatch branch). Dispatch style decides how the program grows.
In the functional version the menu validation lives in the same operators dict — add '5': power there and the menu entry. You never touch calculate().
The imperative version forgets every result. Give it a history — without using any global variables.
What to notice: you’ll end up passing the history in and returning a new one out… which is exactly the functional approach. State has to live somewhere; the paradigms differ in who is allowed to hold it and who may change it.
Change main() to keep history = [], pass it into calculate(), and append there. Now ask yourself: who else can see that list? In the OO version the answer is “only the object”; in the functional version the answer is “nobody can change it, ever”.
Write a tiny test script (no framework needed) that proves the functional
update_history never mutates its input:
h1 = ()
h2 = update_history(h1, 42)
assert h1 == () # old history untouched
assert h2 == (42,)
Now try the equivalent experiment on the OO calculator.history list.
Can you make the same guarantee? Why not?
Try same = calculator.history then call calculator.add(1, 1). Did same change? That’s aliasing — the reason mutable shared state needs discipline.
The OO main() dispatches with if/elif. Replace it with a dict
mapping choice → bound method, the way the functional version maps choice → function:
actions = {
"1": calculator.add,
"2": calculator.subtract,
# ...
}
result = actions[choice](x, y)
What to notice: paradigms aren’t walls, they’re defaults. Python lets you borrow the best trick from each — a bound method is a first-class function.
Build the dict after creating calculator so the bound methods exist. Bonus: what breaks if you build it before?
When you hit run, this page loads the exact
imperative,
functional and
object-oriented
calculator.py files from the repo into
Pyodide (CPython compiled to WebAssembly), and calls into each
module the way its paradigm demands: a string choice for imperative, a function pulled from a dict
for functional, and a method on a Calculator instance for OO. Nothing is re-implemented
in JavaScript — the code you see is the code that runs. (The files are served from bundled
copies in docs/paradigms/ so the site stays stable while the repo is experimented on.)