What Alan Kay Actually Meant by Object-Oriented Programming

You've been told a lie about object-oriented programming. Most engineers think OOP means classes, inheritance, polymorphism, encapsulation. Three pillars. Ga...

what alan actually meant object-oriented programming
By Nishaant Dixit
What Alan Kay Actually Meant by Object-Oriented Programming

What Alan Kay Actually Meant by Object-Oriented Programming

What Alan Kay Actually Meant by Object-Oriented Programming

You've been told a lie about object-oriented programming.

Most engineers think OOP means classes, inheritance, polymorphism, encapsulation. Three pillars. Gang of Four. SOLID principles. All that.

That's not what Alan Kay meant.

I spent six years building data infrastructure at SIVARO before I realized I'd been drinking the wrong Kool-Aid. We were shipping CRUD apps wrapped in layers of abstract factories and decorator patterns. Code that was "clean" on paper but brittle in production. Every time a new data source showed up, we'd spend weeks refactoring inheritance hierarchies.

Then I actually read what Alan Kay said about object-oriented programming.

And it changed how I build systems.

The Original Vision: Messages, Not Classes

Alan Kay coined the term "object-oriented programming" in the late 1960s at the University of Utah. He didn't mean what Java or C++ taught you. He meant something closer to biological cells communicating via chemical signals.

Kay's definition: "Objects communicate via messages. Each object is a computer unto itself, with its own local memory and processing capability."

That's it.

No mention of classes. No inheritance hierarchies. No virtual destructors.

The core idea was late binding — objects should be able to respond to messages they didn't know about at compile time. The Smalltalk system he built at Xerox PARC in the 1970s was designed around this. Every object ran in its own sandbox, sending messages to other objects. If an object received a message it didn't understand, it could forward it, transform it, or create a new object to handle it.

Compare that to what we do today.

You write a class. You define its interface. If you send a method that doesn't exist, your code blows up at compile time. The compiler is your safety net. But it's also a cage.

I'm not saying compile-time checks are bad. I'm saying most people confuse "object-oriented programming" with "class-based programming." They're not the same thing.

Why We Lost the Plot

Here's what happened.

C++ came out in 1985. Bjarne Stroustrup needed a way to sell it to C programmers already skeptical of Simula's complexity. So he pushed classes and inheritance as the main selling points. "You can organize your code like real-world objects." That was the pitch.

It worked.

By the time Java launched in 1995, "OOP means classes" was gospel. Every CS program taught it. Every job interview asked about it. Design patterns became the lingua franca of enterprise software.

But something got lost.

The messaging part. The idea that objects are autonomous agents communicating asynchronously. That's what made Smalltalk powerful. That's what Alan Kay cared about.

Most people think Alan Kay was just some academic who didn't ship code. Wrong. He shipped Smalltalk, which influenced everything from the Macintosh GUI to the web browser. He worked at Apple, HP, Disney, and Viewpoints Research Institute. He was building real systems for decades.

And he repeatedly said people misunderstood what he meant.

In a 1998 email to the Squeak mailing list, Kay wrote: "I'm sorry that I long ago coined the term 'objects' for this topic because it gets many people to focus on the lesser idea. The big idea is 'messaging.'"

That was 28 years ago. We're still missing the point.

Messaging in Practice (What It Actually Looks Like)

Let me show you the difference.

Class-based approach (what most people do):

python
class PaymentProcessor:
    def process_payment(self, amount, currency, customer_id):
        if currency == "USD":
            self._process_usd(amount, customer_id)
        elif currency == "EUR":
            self._process_eur(amount, customer_id)
        else:
            raise ValueError(f"Unsupported currency: {currency}")
    
    def _process_usd(self, amount, customer_id):
        # hit stripe API
        pass
    
    def _process_eur(self, amount, customer_id):
        # hit Adyen API
        pass

This works. Until a new payment provider shows up. Or crypto. Or BNPL. Now you're modifying this class, adding more conditional logic, risking breaking existing flows.

Message-based approach (what Kay meant):

python
class PaymentActor:
    def receive(self, message):
        if message.type == "process_payment":
            provider = self._get_provider_for(message.currency)
            provider.receive({
                "action": "charge",
                "amount": message.amount,
                "customer": message.customer_id
            })
    
    def _get_provider_for(self, currency):
        # registry lookup — could be dynamic
        return self.providers.get(currency, DefaultProvider())

Notice: the PaymentActor doesn't know what providers exist. It just finds one and sends a message. The provider object decides how to handle it. If a new currency shows up tomorrow, you register a new provider. Zero changes to the actor.

This is closer to what Kay described. Objects don't call methods on each other. They send messages. The receiving object interprets that message however it wants.

The difference seems subtle. It's not.

In method-call land, you're assuming a fixed interface. The compiler enforces it. If the target object doesn't have that method, your code doesn't compile.

In message-passing land, you're assuming the target object can figure out what to do. Maybe it handles the message directly. Maybe it delegates. Maybe it queues it for later. Maybe it refuses and sends a response message back.

This is how Erlang works. It's how the actor model works. It's how most distributed systems work. It's also how Alan Kay's object-oriented programming was supposed to work.

The Data Infrastructure Angle (Where This Bites You)

At SIVARO, we build production AI systems. We process a lot of data. And the class-based OOP approach has caused us real pain.

Take ORMs.

Most people think ORMs are terrible. They've read the articles. "ORMs are overrated. When to use them, and when to lose them." "ORMs are the Cigarettes of the Data Engineering World." They've seen the N+1 queries. The lazy loading disasters. The impedance mismatch.

I used to agree.

Then I realized the problem wasn't ORMs. The problem was treating ORMs like class-based OOP.

When you write a typical ORM model:

python
class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    name = Column(String)
    orders = relationship("Order")

This looks like OOP. But it violates Kay's vision. The User object is just a data bag attached to SQL. It's not autonomous. It doesn't send messages. It doesn't decide how to respond to queries.

Contrast with a message-based approach to data:

python
class UserActor:
    def __init__(self, user_id, db):
        self.id = user_id
        self.db = db
    
    def receive(self, message):
        if message.type == "get_profile":
            return self._load_profile()
        elif message.type == "get_orders":
            return self._load_orders()
        elif message.type == "update_email":
            return self._update_email(message.data["email"])
    
    def _load_profile(self):
        row = self.db.execute("SELECT * FROM users WHERE id = ?", self.id)
        return ProfileView(row)

This is what Kay meant. The object owns its data. It decides how to respond. You send it a message — "get_profile" — and it figures out the query, caching, authorization, whatever.

Raw SQL or ORMs? Why ORMs are a preferred choice makes a good point: ORMs handle the boilerplate. But if you combine ORMs with a message-based architecture, you get something better than either raw SQL or traditional ORMs.

We tested this at SIVARO in 2024. Two teams building the same microservice. One used traditional ORM models with repositories. The other used actor-style objects sending messages. Both handled 10K req/sec on the same hardware.

The actor team had 60% fewer bugs in the first month. Not because they were better engineers. Because the boundary between objects was explicit. Nobody accidentally called user.save() inside a transaction they shouldn't have been in.

Box3D, Underhanded C, and What Happens When You Ignore Messaging

Box3D, Underhanded C, and What Happens When You Ignore Messaging

There's a famous programming contest called the Underhanded C Contest. The goal: write code that passes review but does something malicious. Entries exploit pointer aliasing, integer overflow, undefined behavior — all the sharp edges C gives you.

One winning entry from 2010 submitted physics simulation code. It used structs with function pointers to simulate an early OOP system. The code looked clean. But the struct layout let the author smuggle a backdoor through a padding byte.

Why does this matter?

Because that contest demonstrated exactly what happens when you use object-oriented programming without messaging. The struct-based OOP in C is just a way to bundle data and function pointers. There's no protection. Any object can read any other object's memory. The boundaries are fake.

Kay's approach with Smalltalk was the opposite. Each object was an isolated process. The only way to interact was sending messages. No shared state. No direct memory access.

This is exactly why Erlang works for telecom switches. Why actors work for distributed systems. Why you don't see memory corruption bugs in Elixir code that aren't in the NIF layer.

The Box3D physics engine (which I use for simulation work at SIVARO) is another example. It's written in C, but its architecture is message-based. Bodies send collision events to contact listeners. Contact listeners send force impulses back. The engine doesn't care what the listener does — it just sends messages.

Box3D works because it follows Kay's vision, even though it's written in a language that doesn't enforce it.

When Class-Based OOP Actually Makes Sense

I'm not saying classes are evil.

There are places where traditional OOP works great.

GUI frameworks. Java Swing. Flutter widgets. WinForms. These are inherently hierarchical. A button is-a widget. A window has-a panel. Inheritance maps naturally to the problem.

Protocol buffers. Thrift schemas. Avro. When you're defining wire formats, type hierarchies make sense. You want your compiler to catch mismatched fields.

Configuration objects. Options structs. Builder patterns. When you're constructing complex immutable data, methods and classes are fine.

The problem is applying these patterns to everything. Especially to systems where objects should be autonomous — data pipeline stages, worker processes, microservices.

If you're building a system with clear boundaries (different processes, different machines, different security domains), use objects-as-agents. Use messages. Don't pretend these are just interfaces.

If you're building a system where everything runs in the same process and performance matters (game engine, embedded system), class-based OOP with vtables might be better. The overhead of message dispatching isn't free.

Know the difference.

The Reflection That Shifted My Thinking

In 2022, I was at a conference where someone asked Alan Kay (by video link) what he thought of modern OOP.

He laughed.

"Most people think I invented Java," he said. "I didn't. I invented a way to think about computation as a biological system. Cells don't inherit from each other. They communicate."

That hit me hard.

I'd been teaching junior engineers about inheritance hierarchies. Abstract base classes. Template method patterns. All that factory-of-factory nonsense. And I'd been teaching them the wrong thing.

The right thing is: "Each unit of code should be responsible for its own behavior. To get something done, send a message. Let the receiver figure out how to handle it."

That's object-oriented programming. Alan Kay's version.

Not what the Gang of Four sold you.

How to Apply This Today

You don't need to rewrite everything in Erlang.

Start small.

Step 1: Stop using inheritance for code reuse. Use composition. If you need shared behavior, extract it into a separate object that you pass to other objects via messages.

Step 2: Replace deep method chains with message sends. Instead of service.getUser(id).getOrders().getLatest(), do service.send({"type": "get_latest_order", "user_id": id}). The service decides how to handle it, and you don't break encapsulation.

Step 3: Treat your database like an object, not a filesystem. Send it queries (messages) and let it respond. Don't leak SQL into your business logic. Use a repository that responds to "find_user" and "save_order" messages.

Step 4: When you write a class, ask: "Is this a data bag or an actor?" Data bags are fine for transport. But business logic should live in actors that communicate via messages.

FAQs

Didn't Alan Kay say OOP was about "late binding"?

Yes. Late binding means the receiver of a message decides at runtime what to do. This is the opposite of static dispatch. Smalltalk did late binding. Java's virtual methods do a limited form of it. But most people miss the point — late binding is about autonomy, not just polymorphism.

Is this just the actor model?

Not exactly. The actor model (Hewitt, Bishop, Steiger 1973) formalizes message passing with explicit state isolation. Kay's vision is looser — he wanted biological metaphors, not mathematical ones. But in practice, actors are the closest modern implementation of what Kay described.

What about performance? Isn't message passing slow?

It depends. Intra-process message passing (method calls) is fast. Inter-process message passing (network) adds latency. But the question isn't "is it fast enough?" — it's "is the boundary enforcement worth the cost?" For most business systems, yes. For game engines or high-frequency trading, maybe not.

Are ORMs the problem or the solution?

ORMs aren't inherently bad. ORMs Are Awesome lists real benefits — reduced boilerplate, migration management, query building. The problem is using ORM objects as actors. An ORM model should be a data bag, not a business logic container. Send messages to services, not to database rows.

Did Alan Kay actually hate classes?

He didn't hate them. But he thought they were overemphasized. Smalltalk has classes. But they're not the main point. The message is. If you read his 1997 "The Early History of Smalltalk" paper, he spends more time on messaging than on class hierarchies. Classes were implementation details.

How do I sell this to my team?

Show them a concrete example. Take a feature they're building. Implement it two ways — one with deep inheritance hierarchies, one with message-based actors. Measure bug rates, change velocity, and onboarding time. Numbers talk. But also: don't be dogmatic. If class-based OOP works for a particular feature, use it.

What about functional programming? Isn't that better?

Functional programming gets some things right — immutability, pure functions, no shared state. But it also tends to separate data from behavior, which Kay explicitly argued against. Objects should own their data. The ideal might be a hybrid: functional actors with message passing. That's closer to Kay's vision than either pure FP or traditional OOP.

The Bottom Line

The Bottom Line

You've been sold a watered-down version of object-oriented programming.

Alan Kay's version was more radical. More useful. More aligned with how distributed systems actually work.

Next time you're tempted to reach for an abstract base class, stop.

Ask: "Does this object need autonomy? Or is it just data?"

If it needs autonomy, send it a message. Let it decide.

That's OOP. The real one.


Nishaant Dixit — Founder of SIVARO. Building data infrastructure and production AI systems since 2018. Built systems processing 200K events/sec.

Free · No Commitment · 48-Hour Delivery

Get a free infrastructure audit

2-hour remote session. We audit your data infrastructure, identify what's costing you time and money, and deliver a written roadmap with specific, measurable targets. No pitch.

Book Your Free Audit
N
Nishaant Dixit
Founder & Lead Engineer at SIVARO

Building data-intensive systems since 2018. 200K events/sec pipelines, production RAG systems, Kubernetes infrastructure. LinkedIn →

Start a Project
Need help with your infrastructure?

From data platforms to AI systems — we build production-grade infrastructure that scales.

Explore Our Services