The Playground

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.

Imperative

Tell the machine how, step by step

call
result
state

View annotated source

Functional

Describe what with pure functions

call
result
state

View annotated source

Object-Oriented

Bundle data + behaviour into objects

call
result
state

View annotated source

So what’s actually different?

ImperativeFunctionalObject-Oriented
Core ideaDo this, then this, then thisCompute values from valuesObjects own data + behaviour
Dispatchif/elif on a stringDict of functions (operators['1'])Method call on an object
HistoryNone — result is forgottenNew tuple returned; old one untouchedObject mutates self.history
Loopwhile TrueRecursion (repl calls itself)while True
Watch out forLogic and I/O get tangled as it growsRecursion depth; takes a mindset shiftShared mutable state; boilerplate for tiny programs

Calculator Katas

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.

Kata 1 · The Rewrite warm-up

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?

Hint

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.

Kata 2 · Power Up warm-up

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.

Hint

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().

Kata 3 · State Your Business intermediate

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.

Hint

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”.

Kata 4 · Prove Immutability intermediate

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?

Hint

Try same = calculator.history then call calculator.add(1, 1). Did same change? That’s aliasing — the reason mutable shared state needs discipline.

Kata 5 · Data-Driven Dispatch stretch

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.

Hint

Build the dict after creating calculator so the bound methods exist. Bonus: what breaks if you build it before?

How this page works

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.)