Skip to main content
Bethemesh
HistoryComputing history

Object-oriented programming: objects, messages, and reusable abstractions

From Simula and Smalltalk to modern classes and prototypes, understand objects, messages, encapsulation, polymorphism, and the limits of inheritance.

Published 10 August 2026Reading : 17 minBy Bethemesh Team
Beginner
Object-oriented programming represented through objects, messages, and reusable abstractions
Show contents
  1. Before objects: procedures, data, and growing complexity
  2. Simula: representing actors in a simulation
  3. Class and object: two different concepts
  4. Alan Kay and another vision of objects
  5. Smalltalk: everything becomes an object
  6. Objects as state, identity, and behavior
  7. Encapsulation: protecting details
  8. Abstraction: exposing what matters
  9. Inheritance: reuse or specialization
  10. Polymorphism: different objects, one contract
  11. Virtual methods and dynamic dispatch
  12. C++: objects meet systems programming
  13. Objective-C: message passing on top of C
  14. The 1990s: object orientation becomes mainstream
  15. Java: a more constrained object model
  16. C# and the .NET ecosystem
  17. Python: objects without forcing one style
  18. JavaScript: prototypes rather than classical classes
  19. Composition over inheritance
  20. Interfaces and dependencies
  21. SOLID: formalizing design principles
  22. Design patterns: naming recurring solutions
  23. UML and object modeling
  24. Testing and objects
  25. Criticism of object-oriented programming
  26. Functional programming challenges mutable state
  27. Rich objects or simple data?
  28. Objects in graphical interfaces and games
  29. Objects and distributed systems
  30. What actually remains from OOP?
  31. A family of models, not one definition
  32. Why learn object-oriented programming today?
  33. Key takeaways
  34. Frequently asked questions
  35. What is object-oriented programming?
  36. Who invented object-oriented programming?
  37. What is the difference between a class and an object?
  38. What are the main principles of OOP?
  39. Is inheritance required?
  40. Is JavaScript object-oriented?
  41. Is Python object-oriented?
  42. Is object-oriented programming obsolete?
  43. Should composition be preferred to inheritance?
  44. Why is OOP still important?

Object-oriented programming, usually abbreviated OOP, is so familiar today that it is easy to forget that it emerged from a specific historical evolution in the way software was designed. Classes, objects, methods, inheritance, interfaces, and polymorphism are now part of the everyday vocabulary of many developers. Yet these ideas did not appear at the same time, and they were never originally intended as a universal recipe.

Object orientation grew from a practical question: how can a program represent entities that have state, behavior, and interactions with other entities?

From Simula to Smalltalk, and later from C++ to Java, Python, and JavaScript, the answer changed considerably. The history of OOP is therefore not the history of one fixed technique. It is the history of a family of ideas: keeping data close to behavior, hiding implementation details, sending messages, substituting different objects behind a common contract, and building reusable abstractions.

Understanding that history also helps explain modern debates. Is inheritance always desirable? Should composition be preferred? Must a language be entirely object-oriented? And, more fundamentally, what exactly is an “object”?

Before objects: procedures, data, and growing complexity

Early programs were closely tied to the operation of the machine. As high-level languages developed during the 1950s and 1960s, programmers gained functions, procedures, data structures, and modules for organizing increasingly large programs.

Procedural programming already provided a crucial abstraction: instead of thinking only in machine instructions, developers could divide a problem into operations.

But systems kept growing.

Data representing one entity could be manipulated by many functions scattered across a program. Changing the internal representation of that data could require changes in many different places. Developers therefore began looking for ways to bring data closer to the operations responsible for it.

One of the most influential answers emerged from simulation.

Simula: representing actors in a simulation

During the 1960s, Norwegian computer scientists Ole-Johan Dahl and Kristen Nygaard worked on languages designed for simulation.

Their problem was particularly well suited to a new way of thinking.

A simulation can contain customers, machines, vehicles, queues, or events. Each entity has its own state and changes over time.

With Simula 67, Dahl and Nygaard introduced mechanisms that would later be recognized as foundational to object-oriented programming: classes, objects, inheritance, and virtual methods.

A class could describe a category of entities. An object represented a particular instance of that category.

This was a powerful shift. A program could be structured around the actors in the simulated domain rather than only around a sequence of procedures.

Class and object: two different concepts

In the classical model popularized by Simula and many later languages, a class defines common structure and behavior.

An object is a concrete instance created from that definition.

A Car class might define state such as speed and fuel level, along with operations such as accelerating or braking. Two objects created from that class share the same general organization but keep separate state.

This distinction became central to C++, Java, C#, and many other languages.

But it is not universal.

Some object systems rely more heavily on prototypes, and some languages treat classes themselves as objects.

The history of OOP therefore shows from the beginning that there is no single technical definition of an object.

Alan Kay and another vision of objects

In the late 1960s and early 1970s, Alan Kay developed a vision that would deeply influence personal computing.

His role in Smalltalk and the Dynabook is explored in our biography of Alan Kay.

Kay imagined systems composed of many autonomous entities communicating with one another.

In this view, the essential idea was not primarily the class or inheritance. It was objects exchanging messages.

Each object protected its internal state and decided how to respond to messages it received.

This model resembles a collection of small computers cooperating with one another more than a large data structure manipulated from outside.

Smalltalk: everything becomes an object

At Xerox PARC, the ideas of Alan Kay, Dan Ingalls, Adele Goldberg, and other researchers led to the Smalltalk family of languages.

Smalltalk pushed object orientation much further than Simula.

Numbers are objects.

Collections are objects.

Classes themselves participate in the object model.

Interaction is based on message sending.

This consistency makes the system remarkably expressive.

Smalltalk did not merely introduce object-oriented syntax. It offered a complete environment in which programs could be explored, modified, and executed interactively.

The combination of language, graphical environment, and development tools had an enormous influence on later programming systems and IDEs.

Objects as state, identity, and behavior

A useful way to understand an object is to separate three dimensions.

State is the information it retains.

Behavior is the set of operations it can perform or messages it can answer.

Identity means that two objects can contain the same values while still representing distinct entities.

This combination distinguishes an object from a simple value.

In a banking application, two accounts can have exactly the same balance without being the same account. Their identities matter.

In other domains, immutable values may be preferable and object identity may be largely irrelevant.

OOP therefore provides a particularly useful model for some categories of problems, but not necessarily for all of them.

Encapsulation: protecting details

Encapsulation is one of the most important ideas in object-oriented design.

It groups state with the operations that manipulate it and restricts direct access to selected implementation details.

Imagine an object representing a bank account.

If every part of a program can directly modify its balance, enforcing rules such as transaction recording or withdrawal restrictions becomes difficult.

By requiring changes to pass through controlled methods, the object can protect its invariants.

Encapsulation is therefore more than declaring fields private. Its purpose is to reduce dependencies between parts of a program and allow an abstraction to change internally without breaking all of its users.

Abstraction: exposing what matters

Encapsulation is closely connected to abstraction.

A useful abstraction exposes what a component can do without requiring its users to understand every detail of how it works.

A File object might provide operations to read, write, or close a resource. Client code does not necessarily need to know which operating-system calls are performed underneath.

This separation makes programs easier to understand.

It also allows one implementation to replace another when the public contract remains stable.

Object orientation did not invent abstraction; every major programming paradigm uses it in some form. OOP popularized a particular form centered on entities combining state and behavior.

Inheritance: reuse or specialization

Inheritance allows one class to acquire characteristics of another and optionally specialize them.

In a traditional example, a Vehicle class may define common behavior while Car and Motorcycle add their own details.

The mechanism feels natural and was long presented as one of OOP’s principal advantages.

It can factor shared code and express taxonomies.

But inheritance also creates strong coupling between base and derived classes.

A poorly designed hierarchy can become rigid, difficult to understand, and risky to modify.

With experience, object-oriented practice learned to treat inheritance as a useful tool rather than the mechanism that should automatically be used for every form of reuse.

Polymorphism: different objects, one contract

Polymorphism is often more fundamental than inheritance itself.

The idea is that the same code can work with different kinds of objects as long as they provide the expected behavior.

A function responsible for drawing shapes might manipulate circles, rectangles, and triangles through a common draw() operation.

The caller does not need to know every implementation detail of every type.

In some languages, this polymorphism relies on inheritance and virtual methods.

In others, it uses interfaces, protocols, duck typing, structural typing, or generic mechanisms.

This diversity reveals the deeper value: programming against a contract rather than one exact implementation.

Virtual methods and dynamic dispatch

Simula and later C++ popularized methods whose exact implementation can be selected at runtime according to an object’s actual type.

This is known as dynamic dispatch.

Suppose a variable is handled as an Animal but actually refers to a Dog.

Calling a virtual makeSound() method can execute the dog’s implementation.

This mechanism enables powerful runtime polymorphism.

It also adds conceptual cost. Understanding which method will execute can require knowledge of a hierarchy and method-resolution rules.

Object-oriented languages therefore make different trade-offs among flexibility, performance, predictability, and readability.

C++: objects meet systems programming

Beginning in 1979, Bjarne Stroustrup sought to combine Simula-inspired abstractions with the efficiency of C.

The project became C++.

C++ played an enormous role in bringing object-oriented programming into industrial software development.

Classes, constructors, destructors, inheritance, virtual functions, and overloading provided rich abstractions while preserving close control over resources.

Yet C++ never became exclusively object-oriented.

It retained procedural programming and later developed generic programming extensively through templates.

This history matters: the success of OOP did not require every other programming paradigm to disappear.

Objective-C: message passing on top of C

Another historical branch also combined C with object ideas: Objective-C.

Created in the early 1980s by Brad Cox and Tom Love, it was strongly influenced by Smalltalk’s message-passing model.

The language added a dynamic object layer to C.

Objective-C became especially important in the NeXT ecosystem and later at Apple after the acquisition of NeXT.

For many years, it was the principal language for macOS and iOS development before Swift became dominant.

Its history shows how two languages starting from similar C roots could interpret object orientation very differently: C++ emphasized compiled abstractions and resource control, while Objective-C leaned more strongly toward dynamic message sending.

The 1990s: object orientation becomes mainstream

During the 1990s, object-oriented programming moved from an innovative approach to a dominant industrial model.

Graphical user interfaces were particularly well suited to object modeling.

A window, button, menu, or event could naturally be represented as an entity with state and behavior.

Object-oriented analysis and design methods multiplied.

Terms such as object-oriented analysis, design patterns, and UML entered professional practice.

OOP sometimes became more than a set of language mechanisms. It was presented as a general way to design almost any software system.

That popularity produced both lasting improvements and significant excesses.

Java: a more constrained object model

Java arrived in 1995 with syntax familiar to C and C++ programmers but a different execution model and automatic memory management.

Our article on the history of Java covers that evolution, while the biography of James Gosling explores the language’s creation.

Historically, Java placed classes at the center of its programming model.

Memory is managed by garbage collection.

The language avoids multiple inheritance of classes while providing interfaces for expressing multiple contracts.

This combination spread object-oriented concepts throughout companies, universities, and programming education.

For an entire generation, learning programming became closely associated with learning to think in classes and objects.

C# and the .NET ecosystem

In the early 2000s, C# adopted many ideas familiar to Java and C++ developers while integrating them with Microsoft’s .NET platform.

Classes, interfaces, properties, exceptions, garbage collection, and polymorphism formed the core of the early model.

The language later became increasingly multiparadigm through generics, lambdas, LINQ, records, pattern matching, and other features.

That evolution is revealing.

Even languages strongly associated with OOP eventually incorporated functional, declarative, and data-oriented ideas.

Objects remained important, but they were no longer treated as the only abstraction a modern language needed.

Python: objects without forcing one style

Python illustrates another approach.

The language fully supports classes, objects, inheritance, and polymorphism, but it does not require every program to be organized around classes.

A function can simply remain a function.

A small script can manipulate lists and dictionaries directly.

Functions themselves are objects and can be passed as values.

Python therefore combines multiple styles naturally.

Its dynamic model also encourages duck typing: instead of requiring an object to belong to one precise hierarchy, code can often care mainly about which operations the object supports.

This brings polymorphism closer to the idea of a behavioral protocol.

JavaScript: prototypes rather than classical classes

JavaScript, created by Brendan Eich, demonstrates even more clearly that object orientation does not necessarily mean classical classes.

Historically, JavaScript uses prototypes.

An object can delegate property lookup to another object serving as its prototype.

Modern JavaScript includes class syntax, but that syntax still operates over the underlying prototype mechanism.

This model is important to the history of OOP.

Classes are an extremely common way to construct and organize objects, but they are not the universal definition of object orientation.

Composition over inheritance

With experience, one recommendation became widespread: favor composition over inheritance when it produces a more flexible design.

Composition builds an object from other objects to which it delegates responsibilities.

A car can contain an engine rather than “inherit from” an engine.

The distinction between an “is-a” relationship and a “has-a” relationship sounds simple, but it prevents many artificial class hierarchies.

Composition often reduces coupling and makes components easier to replace.

It does not make inheritance useless.

It simply reminds developers that code reuse does not require constructing a tree of classes.

Interfaces and dependencies

One of the most important developments in object-oriented design is separating what a component needs from the exact implementation that satisfies that need.

A class that must save data can depend on a Storage interface rather than one specific database.

One implementation can use PostgreSQL, another a file, and another an in-memory fake for testing.

This approach makes replacement and testing easier.

It lies behind dependency injection and several SOLID principles.

But value does not come from maximizing the number of interfaces. An abstraction is useful when it represents a real boundary of responsibility or variation.

SOLID: formalizing design principles

The term SOLID groups five design principles commonly associated with Robert C. Martin: single responsibility, open/closed, Liskov substitution, interface segregation, and dependency inversion.

These principles aim to reduce the impact of change in object-oriented systems.

They have had enormous influence in professional software development.

But they are best understood as heuristics, not mathematical laws.

Applying every principle mechanically can produce an explosion of tiny classes, interfaces, and abstraction layers with little practical benefit.

Good design remains contextual.

The history of OOP is partly a movement from enthusiastic rules toward more nuanced engineering judgment.

Design patterns: naming recurring solutions

In 1994, Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, popularized 23 design patterns.

Factory, Observer, Strategy, Decorator, and Adapter became shared vocabulary.

The purpose of patterns is not to provide code that should simply be copied.

They give names to recurring design structures, making it easier for developers to communicate.

Their popularity also produced a side effect: some projects began applying patterns even when the language or problem allowed a much simpler solution.

A pattern is a tool for understanding, not an architectural goal.

UML and object modeling

During the 1990s, several object-oriented modeling methods competed.

Work by Grady Booch, James Rumbaugh, and Ivar Jacobson eventually converged into UML, the Unified Modeling Language.

Class diagrams became particularly associated with object design: classes, relationships, inheritance, and associations could be represented graphically.

UML was standardized and widely adopted in some large organizations.

Its use varies today.

Agile teams often prefer lighter diagrams, but UML concepts remain useful when precise communication about a complex architecture is required.

The important point is not to confuse the model with the software itself: a diagram supports reasoning; it does not replace working code.

Testing and objects

OOP also influenced testing practices.

Encapsulation makes it possible to test a component through its public behavior.

Interfaces and dependency injection can replace selected dependencies during tests.

This encouraged mocks, stubs, and fakes.

But an architecture fragmented solely to make everything mockable can become harder to understand.

Modern practice therefore tends to balance unit tests, integration tests, and tests of real behavior.

Good object design improves testability when it creates coherent boundaries, not when it turns every line of code into an interface.

Criticism of object-oriented programming

As OOP became dominant, criticism grew.

Deep inheritance hierarchies can be fragile.

Shared mutable state can make behavior difficult to reason about.

Large networks of tiny interconnected objects can hide the actual flow of data.

Premature abstractions increase complexity.

Some so-called enterprise architectures accumulated layers, factories, and interfaces until simple operations became difficult to follow.

These criticisms do not prove that objects are bad.

They show that a widely adopted paradigm will eventually be applied to problems for which it is not always the best tool.

Functional programming challenges mutable state

The rise of functional programming particularly challenged the importance of mutable state.

Pure functions and immutable data can simplify reasoning, parallelism, and testing.

Historically object-oriented languages increasingly incorporated functional ideas.

Java added lambdas and streams.

C# developed LINQ and lambda expressions.

C++ strengthened its lambda support.

Python and JavaScript have long treated functions as values.

The opposition between “object-oriented” and “functional” therefore became less useful.

Modern languages and applications often combine both approaches according to the problem being solved.

Rich objects or simple data?

Another debate contrasts rich domain objects, which contain rules and behavior, with simpler data structures manipulated by functions or services.

In a complex domain model, placing rules close to data can protect invariants and make business concepts explicit.

In a data-transformation pipeline, simple structures and composable functions may be much clearer.

There is no universal answer.

The right choice depends on factors such as entity lifetime, identity, invariants, concurrency, and the nature of transformations.

This nuance is essential if OOP is to remain an engineering tool rather than a doctrine.

Objects in graphical interfaces and games

Some domains remain naturally compatible with object models.

In a graphical interface, buttons, windows, fields, and controllers often have state, identity, and reactions to events.

In games, players, vehicles, and interactive elements can likewise be modeled as objects.

Even in these domains, architecture has evolved.

Game engines frequently use Entity Component System (ECS) models that emphasize composition and data separation instead of deep inheritance hierarchies.

This illustrates a broader trend: retain useful notions of entities while reducing rigid dependencies between classes.

Objects and distributed systems

The early idea of autonomous entities exchanging messages also echoes through distributed systems.

The actor model, associated with work by Carl Hewitt and later used in languages such as Erlang and frameworks such as Akka, organizes computation around entities that receive messages and maintain private state.

An actor is not exactly an object in the Java or C++ sense, but the conceptual relationship is striking.

Alan Kay emphasized messaging and isolation more strongly than classes themselves.

At large scale, modern systems therefore rediscover some old object-oriented intuitions in new forms.

What actually remains from OOP?

After several decades, some object-oriented ideas appear more durable than others.

Encapsulation remains essential: reducing how much one component must know about another limits coupling.

Polymorphism remains powerful: multiple implementations behind a contract support evolution.

Composition has become a central construction technique.

Identity and state remain useful for modeling many real entities.

By contrast, the idea that good architecture must necessarily consist of a large class hierarchy has lost much of its influence.

Modern object-oriented programming is often more restrained than the version taught during the 1990s.

A family of models, not one definition

Asking whether a language is “truly object-oriented” often leads to endless arguments.

Smalltalk treats almost everything as an object and centers message sending.

C++ provides classes while retaining primitive mechanisms and several paradigms.

Java historically structured most code around classes while also having primitive types.

Python treats many things as objects but allows highly procedural programming.

JavaScript historically relies on prototypes.

These systems differ enough to show that OOP is better understood as a family of programming models sharing several ideas rather than as one absolute checklist.

Why learn object-oriented programming today?

Even when a project makes little use of inheritance, understanding OOP remains essential.

An enormous amount of existing software is organized around classes and objects.

Many frameworks expose object-oriented APIs.

The ideas of encapsulation, responsibility, interfaces, and polymorphism extend beyond strictly object-oriented languages.

Learning OOP also teaches developers to recognize its limitations.

A developer who truly understands the paradigm knows when a class provides a useful abstraction and when a simple function or data structure is enough.

Maturity means choosing the right representation for the problem rather than “using objects everywhere.”

Key takeaways

Object-oriented programming emerged gradually during the 1960s with Simula, designed by Ole-Johan Dahl and Kristen Nygaard to represent actors in simulations.

During the 1970s, Smalltalk and Alan Kay developed a vision centered on autonomous objects communicating through messages.

C++ later spread classes, inheritance, and polymorphism throughout industrial programming without becoming exclusively object-oriented.

In the 1990s, Java helped make OOP a dominant model, while Python and JavaScript demonstrated that objects could coexist with more dynamic and multiparadigm approaches.

Experience also corrected several excesses.

Inheritance is no longer treated as the natural answer to every reuse problem. Composition, interfaces, immutability, and functions play increasingly important roles.

The most durable lesson of object orientation is probably not “everything must be a class.”

It is more general: good software groups coherent responsibilities, hides unnecessary details, and allows components to collaborate without depending excessively on one another’s internal implementation.

Frequently asked questions

What is object-oriented programming?

It is a family of approaches that organizes software around objects that generally have state, identity, and behavior and collaborate through methods, messages, or interfaces.

Who invented object-oriented programming?

There was no single inventor. Ole-Johan Dahl and Kristen Nygaard introduced several foundational mechanisms in Simula. Alan Kay and the Smalltalk team later developed and popularized a different message-centered vision.

What is the difference between a class and an object?

A class usually describes common structure and behavior. An object is a particular instance with its own state. Prototype-based object systems do not necessarily use this model in the same way.

What are the main principles of OOP?

Encapsulation, abstraction, inheritance, and polymorphism are commonly listed. In modern practice, composition, interfaces, and dependency management are equally important.

Is inheritance required?

No. Many object-oriented systems use little inheritance and rely instead on composition, interfaces, or protocols.

Is JavaScript object-oriented?

Yes, it supports object-oriented programming, but its historical model is prototype-based rather than class-based. Modern class syntax still operates on prototypes underneath.

Is Python object-oriented?

Python fully supports object-oriented programming, but it is multiparadigm and does not require every program to be organized into classes.

Is object-oriented programming obsolete?

No. It remains widely used and enormous amounts of software depend on its concepts. It is simply no longer treated as the universal solution to every programming problem.

Should composition be preferred to inheritance?

Often, composition produces more flexible and less tightly coupled components. Inheritance remains useful when a genuine specialization relationship exists and the base contract is stable.

Why is OOP still important?

Because encapsulation, responsibility, contracts, and polymorphism remain valuable tools for controlling complexity, including in systems that combine several programming paradigms.

Sources and references

  1. 1.ACM — The Early History of Smalltalk
  2. 2.Computer History Museum — The Birth of the Object-Oriented Language Simula
  3. 3.Bjarne Stroustrup — A History of C++

Collection

Programming languages

  1. 01Grace Hopper: from early compilers to COBOL
  2. 02John Backus: FORTRAN, BNF, and the rejection of machine code
  3. 03Dennis Ritchie: the C language at the heart of Unix
  4. 04FORTRAN: proving that a compiler could compete with assembly
  5. 05The C language: making systems portable without hiding the machine
  6. 06Niklaus Wirth: from Pascal to Oberon, designing through simplicity
  7. 07Bjarne Stroustrup: designing C++ without giving up performance
  8. 08Pascal: learning to program by making structure visible
  9. 09C++: from C with Classes to a general-purpose language
  10. 10Object-oriented programming: objects, messages, and reusable abstractions
  11. 11Guido van Rossum: creating Python to make code readable
  12. 12Brendan Eich: JavaScript, from Netscape prototype to Web standard
  13. 13James Gosling: the engineer behind Java
  14. 14Python: readability, batteries included, and a global ecosystem
  15. 15Java: write once, run anywhere
  16. 16JavaScript: the language that made the Web interactive
  17. 17Ken Thompson: from Unix to Go, simplicity as a method
  18. 18John McCarthy: Lisp and the idea of programming with symbols
  19. 19Alan Kay: Smalltalk and the computer as a personal medium
  20. 20Barbara Liskov: the abstraction that made software modular
  21. 21Robin Milner: ML, machine-assisted proof, and languages of interaction
  22. 22Brian Kernighan: AWK, Unix, and the art of explaining code
  23. 23Anders Hejlsberg: from Turbo Pascal to C# and TypeScript
  24. 24Larry Wall: Perl, the language that connected the tools of the Internet
  25. 25Yukihiro Matsumoto: Ruby and programmer happiness
  26. 26Rasmus Lerdorf: PHP and the democratization of the dynamic Web
BiographyComputing historyBeginner

James Gosling: the engineer behind Java

How James Gosling and Sun's Green team designed Java: from Oak and virtual machines to portability and the language's lasting legacy.

17 August 20266 minRead

Was this article useful?