Objects at the Command Line #001 — FizzBuzz Without an `if` Statement
FizzBuzz is the interview cliché everyone loves to hate. It’s also a perfect tiny lab for a question I keep coming back to: what does scripting feel like when everything is an object?
Here’s how most of us write it — clear, idiomatic Python, nothing wrong with it:
def fizzbuzz(n):
if n % 15 == 0:
return "FizzBuzz"
elif n % 3 == 0:
return "Fizz"
elif n % 5 == 0:
return "Buzz"
else:
return str(n)
for n in range(1, 21):
print(fizzbuzz(n))
Now the same thing in GNU Smalltalk:
Object subclass: Rule [
| divisor word |
Rule class >> on: aNumber say: aString [
^self new setDivisor: aNumber word: aString
]
setDivisor: aNumber word: aString [
divisor := aNumber.
word := aString
]
appliesTo: n [ ^n \\ divisor = 0 ]
word [ ^word ]
]
| rules |
rules := { Rule on: 3 say: 'Fizz'. Rule on: 5 say: 'Buzz' }.
1 to: 20 do: [:n |
| said |
said := (rules select: [:r | r appliesTo: n])
inject: '' into: [:acc :r | acc , r word].
Transcript showCr: (said isEmpty ifTrue: [n printString] ifFalse: [said])
].
Run it (gst after.st) and you get exactly the FizzBuzz you’d expect.
The thing that reframed it for me
Smalltalk has no if keyword. That ifTrue:ifFalse: up there? It’s a message sent
to a boolean object. true and false are objects that know how to respond to it. The
language didn’t reserve a word for branching — it let objects handle it, like everything else.
Once branching is “just messages,” the design pressure changes. A rule stops being a line
in a conditional ladder and becomes a small object that knows two things: which numbers it
applies to, and what to say. The number never inspects itself. The rules filter
themselves with select:. The words assemble themselves with inject:into:.
Why this isn’t just aesthetics
Add “Bazz on 7.” In the Python ladder you edit the logic — and now you owe every combination: 21, 35, 105. In the Smalltalk version:
rules := { Rule on: 3 say: 'Fizz'. Rule on: 5 say: 'Buzz'. Rule on: 7 say: 'Bazz' }.
One object. No logic touched. The combinations fall out for free, because concatenation of matched words was never special-cased in the first place. That’s the open/closed principle showing up not as a lecture, but as the natural shape of the code.
The transferable lesson
You don’t need Smalltalk for this. The next time you’re adding another branch to a conditional, pause and ask: am I adding logic, or am I adding data? A list of small objects frequently replaces the whole ladder — in Python, JavaScript, Ruby, anywhere — and it stays open for extension without editing what already works.
Honest caveat: for real FizzBuzz, write the three-line version and move on. The object version earns its keep when the rules multiply — pricing tiers, validation chains, discounts, feature flags — the places a conditional ladder quietly becomes a maintenance tax.
Every sample in this series is executed against GNU Smalltalk 3.2.5 before it ships — this one included. I write them in VS Code using my open-source GNU Smalltalk extension. How would you have modeled this — and what should Episode #002 tackle?