Not a day goes by when I dont have a developer DM me asking: “Big Yoda, why do you hate classes so much?” I say: “That’s easy: Performance and quality what else is there?”
The Performance Nobody Talks About
Comp Sci 101: We obsess over time and space complexity. We argue about O(n) vs O(log n). We benchmark our loops. We agonize over hash table load factors. We debate linked lists vs dynamic arrays. We count every recursive call. We optimize our sorting algorithms. We worry about the height of our trees. We calculate the exact number of comparisons. We minimize function calls. We unroll loops for those precious cycles. We argue about tail recursion. We profile every method. We eliminate every unnecessary allocation.
And then we scatter our objects across the heap like a garbage truck losing its load on the freeway during rush hour.
I don’t know who needs to hear this, but every new MyClass()
is another cache miss waiting to happen. Your CPU has these beautiful cache lines, 64 bytes of data it can grab at once. So what do we do? We guarantee that none of our objects are anywhere near each other in memory.
Write a Customer
class with a List<Order>
where each Order
had a List<LineItem>
. Following one customer’s data meant chasing pointers all over the heap.
Replace it with three arrays – customers, orders, line items – and suddenly the same algorithm runs 50x faster. Not 50%. Fifty times.
The Defect Factory: Now With 30% More State Corruption!
But performance is just the appetizer. For the main course, you vill eat ze bugs that classes serve up.
Every class is a petri dish for state corruption. You have ten methods touching five fields. Quick: tell me all the possible states your object can be in. You can’t. Nobody can. The complexity is multiplicative:
- Method A sets field X
- Method B reads field X and sets field Y
- Method C reads Y but only after A and B have run
- Except sometimes C runs first because of threading
- Or B gets called twice
- Or someone forgot to call A
With functions? Input goes in, output comes out. No mysteries. No state. No surprises.
Concurrency? It’ll be fine…
“Just make it thread-safe,” they said. “It’ll be fine,” they said.
Sure. Let me just sprinkle some synchronized
keywords around like fairy dust. Wave my magic mutex wand. Maybe sacrifice a goat to the concurrency gods while I’m at it.
Here’s the dirty secret: You cannot make a stateful class truly thread-safe. You can make individual methods thread-safe. But the sequence of method calls? The invariants between multiple fields? Good luck.
I watched a team spend six months trying to make their domain model “thread-safe”. They added locks everywhere. They created lock ordering protocols. They wrote extensive documentation about which locks to acquire when. They had more mutexes than a prison has doors.
Legend has it that server is still frozen, waiting for locks that will never come.
You know what doesn’t deadlock? Pure functions operating on immutable data.
Objective What About-ism
“But Adam,” you say, “what about objects that model real things? What about encapsulation? What about design patterns?”
Fair questions. I’ve built plenty of systems with these patterns. Factories, visitors, decorators, the whole catalog. They have their place.
But here’s what I’ve noticed: The best parts of these patterns are always the functional parts. The worst parts are always the stateful parts.
Encapsulation? That’s hiding data. But a pure function hides its implementation perfectly – you literally cannot access its internals. Inheritance? We’re composing behaviors – functions do that naturally. Polymorphism? That’s just picking which function to call.
The patterns aren’t wrong per se. They’re just functions in object-oriented drag.
Let’s be reasonable
I am not saying never use classes. If you’re modeling something truly stateful – a file handle, a network connection, a game entity – fine. Use a class. But make it small. Make it obvious. Make the state transitions explicit.
For everything else? Use functions.
Your code will be faster. It will have fewer bugs. It will be easier to test. It will be easier to understand. It will be easier to parallelize.
But most importantly: It will be honest about what it does.
Classes lie. They promise encapsulation but deliver complexity. They promise abstraction but deliver obfuscation. They promise reuse but deliver rigid hierarchies.
Functions tell the truth. This goes in, that comes out. No more, no less.
Choose truth. Choose functions.
Classes are not an abstraction. They are an admission of failure – a failure to find the functions that actually solve your problem.