I spent a Saturday finishing a 30-chapter tutorial for a programming language that turned fifty last year. I write Python. I’ve shipped JavaScript. Nobody is hiring for Smalltalk.
It was the most useful day of learning I’ve had in months, and not for nostalgia reasons. Here’s what actually happened, and what the rest of my Smalltalk summer has looked like.
Why I’m doing this at all
I’m working on a university research project called Zag, which is a Smalltalk virtual machine written in Zig. A VM is the thing that actually runs your code: it takes the language you wrote and turns it into something the processor can execute. CPython is one. The JVM is one. Zag is one, for Smalltalk.
I joined wanting to learn how languages work under the hood. What I found is that you can’t meaningfully work on a VM for a language you can’t read. So before touching the VM, I had to learn the language.
Worth knowing: the Zag repo is about 38,000 lines of Smalltalk and 28,000 lines of Zig. There’s more Smalltalk in it than Zig. The compiler itself is written in Smalltalk. So this wasn’t a detour.
The one idea the whole language is built on
Smalltalk has almost no syntax.
That sounds like marketing until you see what it costs. In Python, 3 + 4 is syntax. The parser knows what + means, and it’s built into the language.
In Smalltalk, 3 + 4 means: send the message + to the object 3, with 4 as the argument. + is a method name. It lives on the integer class. Someone wrote it.
Every single thing I found confusing turned out to be that same idea wearing a different hat:
| What confused me | What was actually happening |
|---|---|
2 + 3 * 4 is 20, not 14 | * is a method, not an operator, so there’s no PEMDAS to apply |
| You can send messages to a class | Classes are objects too |
There is no if statement | ifTrue:ifFalse: is a method on booleans |
There is no for loop | to:do: is a method that runs a function repeatedly |
That last pair is the one that rearranged something for me. Smalltalk doesn’t have if. It doesn’t have for or while either. What it has is a way to pass a chunk of unevaluated code around as a value, and then messages that decide whether and how often to run it.
x > 5 ifTrue: [ 'big' ] ifFalse: [ 'small' ]
The square brackets are a block, which is Smalltalk’s version of a lambda. ifTrue:ifFalse: is an ordinary method that takes two of them and runs one. Control flow isn’t part of the language. It’s a library.
The two moments I actually learned something
Both of my real breakthroughs came from being wrong out loud, which I think is the transferable part of this post.
Wrong guess one. I was asked how many elements #(1 2 3+4) has. I said three. It has five.
#(1 2 3+4) → #(1 2 3 #+ 4)
I read 3+4 as arithmetic. It was never arithmetic. Inside #( ), nothing is computed. The parser just collects whatever tokens you typed, and 3, +, 4 are three separate tokens. The + isn’t doing anything, so it comes out as a bare name.

I would not have remembered that if someone had just told me. Getting five when I confidently predicted three is why it stuck.
Wrong guess two. What does [ 3 + 4 ] evaluate to? I said 7. It’s a block. A function. Not the number 7.
In JavaScript this is obvious the moment you see it:
() => 3 + 4 // a function
(() => 3 + 4)() // 7
I’ve written arrow functions. I still got it wrong in unfamiliar syntax. Which taught me something about the difference between recognizing a concept and actually holding it.
Coming from Python, specifically
The things that transferred cleanly:
- Blocks are lambdas.
[ :x | x + 2 ]islambda x: x + 2. collect:ismap.select:isfilter.reject:isfilterwith the condition flipped.inspectis a much betterdir().

The things that did not:
- Collections are 1-indexed.
'hello' at: 1is the first character. to:is inclusive.1 to: 100includes 100.range(1, 100)stops at 99.- Division gives you exact fractions.
8 - 5 / 2is3/2, not1.5. You ask for a float explicitly if you want one. - There are no functions. None. There’s no place to put a
len(), so it’s'hello' size, a message to the string. The object always comes first, and after a while the word order stops feeling backwards. - Symbols. Python has no equivalent at all. A string is text; a symbol is a name, and there is exactly one of any given symbol in the entire system. Method names are symbols, which is why a VM can look up which method to run by comparing pointers instead of comparing characters.
That last one is where the language and the VM work started touching each other, and it’s the first time a language design choice and a performance consequence were visibly the same decision to me.
So why learn this in 2026
Three honest answers.
Because a lot of what you use came from here. The first real IDE was a Smalltalk environment. So was the first refactoring browser. Unit testing as we practice it started as SUnit, written in Smalltalk by Kent Beck, and JUnit came after. Model-View-Controller came out of this community. So did most of what became agile. You are already using this language’s ideas; you just meet them secondhand.
Because it shows you which of your assumptions are actually language features. I thought operator precedence was a property of arithmetic. It’s a parser convention. I thought if was fundamental to programming. It’s a method call. You don’t find that out by learning a fourth language that made the same choices as your first three.
Because the environment is genuinely different. Smalltalk doesn’t have source files in the way you’re used to. You run an image, which is a snapshot of an entire live system, every object still alive, frozen mid-execution. You browse code by clicking through live classes. You can add a method to a running class and call it a second later. I compiled a new method into the tutorial’s own class, used it, then deleted it, all while the tutorial was running.

Nothing about that is nostalgic. Most of it is still ahead of what I use daily.
The book my supervisor handed me
Alongside the tutorial I’ve been reading Smalltalk Best Practice Patterns, by Kent Beck. My supervisor gave it to me, and it’s from 1996.
It isn’t a book about the language. It’s a book about writing it well: how to name a method, when to break one in half, which collection to reach for. About ninety short patterns, each a couple of pages.
The reason it’s the right book for this project rather than a nostalgia read is that most of what I need to do is read code, not write it. The repo is mostly Smalltalk. You can know every piece of syntax and still find real code impenetrable, because real code is written in idioms, and this is the book that names them.
Here’s the moment that convinced me it was worth the time.
Partway through the tutorial I opened the source of next, the method I’d been running all afternoon to advance lessons. Four lines. And the editor had flagged it with a warning:
Replace single branch conditional with guard clause.
The method wrapped its whole body in an ifTrue:. The suggestion was to bail out early instead:
next
self lessonView isOpenInWindow ifFalse: [ ^ self ].
self player next.
self showCurrentLesson
I’ve written that shape in Python a hundred times without knowing it had a name. Guard Clause is a pattern in the book that was sitting on my desk, and the IDE taught it to me before I’d read that page.
That’s the thing about this book I didn’t expect: the patterns aren’t Smalltalk trivia. Most of them are about how to make code say what it means, and they survive the trip to Python intact.
Two other things from it have stuck.
The first is that the book isn’t really about code. It’s about communication.
Beck’s framing is that the expensive bottleneck in software isn’t the processor, it’s the next person to read the code, and that most of a project’s budget goes on maintenance, which means on people trying to reconstruct what the original author meant. His argument is that you can’t make a human read faster, so the only lever you have is shared vocabulary. If we both know what “Composed Method” means, I can say one word instead of a paragraph.
That reframed the whole book for me. I’d assumed a style guide was about tidiness. It’s closer to information theory.
It also happens to describe the actual job I’ve been given. Part of what I’m meant to do in this group is help new people understand the project, and I’ve been treating that as separate from the technical work. Beck’s position is that it isn’t separate. Making the thing understandable is the work.
The second is Composed Method, and it’s the one I’ve already violated most.
The rule: a method should do one thing, at one level of abstraction. If you’re mixing “parse the input” and “run the algorithm” in the same function, split them. Beck goes further than most people are comfortable with, and defends methods that are a single line, on the grounds that a one-line method with a good name bridges how something is done and what it was for.
My instinct was that this is inefficient. It isn’t, and the objection is about how code looks rather than how brains work. We read in chunks. A well-named method is a chunk.
I know I violate this because my algorithms practice is full of forty-line functions called solve. Those are exactly the code Beck is describing, and I wrote all of them.
The whole thing collapses into one rule he calls Once and Only Once: in a well-written system, every piece of logic is stated exactly once. Everything else in the book is a technique for getting there.
What I’d tell someone starting
Run everything yourself. Predict the answer before you hit evaluate, and say the prediction out loud even when you’re not sure, because a wrong prediction is worth about ten correct explanations.
And when something doesn’t land, stop. I hit a chapter on reflection at the five hour mark and it turned to noise. Not because it was hard, but because I was done. I wrote down that it was worth coming back to and moved on. It’s still worth coming back to.

Next up: actually reading the compiler.
Discover more from Sarah Rose Hassan
Subscribe to get the latest posts sent to your email.
