OOP and Functional — Two Views Coexisting
OOP and Functional — Two Views Coexisting
The question "OOP or functional?" used to be treated like a tribal feud. Today's modern languages support both, and real-world code mixes the two paradigms naturally. This article covers the origin of each paradigm, their core principles, how modern languages absorbed both, and which view fits which spot in practice.
1. About OOP and FP
OOP — the most cited origin is Smalltalk, made by Alan Kay at Xerox PARC in 1972. Before that, Simula 67 (Ole-Johan Dahl, Kristen Nygaard) introduced classes and inheritance in 1967, and Smalltalk crystallized "message passing" as a model. Alan Kay's own remark — "the core of object orientation is message passing, not classes" — is often cited. Mass adoption came with C++ in the 1980s (1985, Bjarne Stroustrup) and Java in 1995, the decisive push.
FP — the root is Lisp from 1958 (John McCarthy). The first practical language built on lambda calculus (1930s, Alonzo Church). Later ML (1973), Miranda (1985), and Haskell (1990) refined the pure functional shape, and Erlang (1986) settled into the concurrency / fault tolerance niche. Mass adoption arrived in the 2010s as the value of side-effect-free code shone again in the multicore era.
2. Four principles of OOP
| Principle | Meaning |
|---|---|
| Encapsulation | Bind data and the behavior acting on it inside one object |
| Inheritance | Children inherit and extend a parent's definition |
| Polymorphism | Different implementations behind the same interface |
| Abstraction | Hide complex internals behind a simple interface |
// Java example
abstract class Animal {
abstract String speak();
}
class Dog extends Animal {
@Override String speak() { return "Woof"; }
}
class Cat extends Animal {
@Override String speak() { return "Meow"; }
}
Animal a = new Dog();
a.speak(); // "Woof" ← polymorphism
Inheritance feels powerful at first, but criticism that deep inheritance trees increase coupling and harm maintainability has accumulated, so since the 2000s "composition over inheritance" is often recommended (also stated in the GoF book).
3. Core ideas of functional
| Idea | Meaning |
|---|---|
| Pure function | Same input always yields same output. No external state mutation |
| Immutability | Data doesn't change after creation. Mutation = create a new value |
| First-class function | Functions are values. Pass as args, return, store in variables |
| Higher-order function | A function that takes or returns functions |
| Referential transparency | Replacing an expression with its result preserves meaning |
| Recursion | Express loops as recursion (including tail-call optimization) |
// imperative
const xs = [1, 2, 3, 4];
let sum = 0;
for (let i = 0; i < xs.length; i++) sum += xs[i] * 2;
// functional
const sum2 = xs.map(x => x * 2).reduce((acc, x) => acc + x, 0);
map, filter, reduce are the most familiar spots where functional thinking has been absorbed into imperative languages.
4. Comparing the two views
| Item | OOP | FP |
|---|---|---|
| State | encapsulated in objects | as immutable as possible |
| Behavior | object methods | free-standing functions |
| Coupling | class hierarchy | function composition |
| Side effects | natural | isolation recommended |
| Concurrency | locks · mutexes needed | natural via immutability |
| Modeling | domain as objects | domain as data + transformations |
Neither is absolutely superior. The same problem fits different shapes naturally in different spots. UI, domain models, simulations lean OOP; data transformation, concurrency, validation lean FP — that's the common reading.
5. Coexistence in modern languages
| Language | Origin | Both supported |
|---|---|---|
| Scala | 2003 | OOP + FP fusion on the JVM |
| Kotlin | 2011 | OOP-default, FP-friendly |
| Rust | 2010 | No OOP. Traits, ownership. Strong FP influence |
| Swift | 2014 | OOP + protocols, value types, higher-order functions |
| TypeScript | 2012 | Classes + first-class functions + immutability recommended |
| Java (8+) | 2014 | Lambdas and Stream API added |
| Python | 1991 | Classes + first-class functions + comprehensions |
| C# | 2002 | Lambdas, LINQ, record types |
Many newer languages aim to hold the good parts of "both" rather than be "OOP only" or "FP only". The paradigm itself is no longer a tribal flag.
6. Other paths
Paradigms beyond OOP / FP:
- Imperative — the oldest branch. C is the representative.
- Declarative — express only "what". SQL, HTML, regex.
- Logic programming — Prolog. Inference from facts and rules.
- Dataflow / Reactive — RxJS, Akka Streams. Data change over time as a graph.
- Actor model — Erlang, Akka. Small actors talk via messages.
- CSP — Go's channels and goroutines.
7. The same task in two views
// Active users from a user list, names only, alphabetical
// OOP-style (hypothetical)
class UserService {
constructor(users) { this.users = users; }
getActiveSortedNames() {
const active = [];
for (const u of this.users) if (u.active) active.push(u);
active.sort((a, b) => a.name.localeCompare(b.name));
return active.map(u => u.name);
}
}
// FP-style
const getActiveSortedNames = users =>
users
.filter(u => u.active)
.map(u => u.name)
.sort((a, b) => a.localeCompare(b));
Same result; the second is shorter and free of side effects.
// React component — a spot where the two paradigms meet naturally
function UserList({ users }) {
const visible = users.filter(u => u.active).map(u => u.name); // FP
return <ul>{visible.map(n => <li key={n}>{n}</li>)}</ul>; // component is OOP-ish
}
8. Common stumbles
Inheritance trap — deep inheritance trees explode the cost of changes. Composition or interfaces / traits are often better.
Cost of immutability — copying large data every time can hurt performance. Immer, Immutable.js, persistent data structures help.
Purity obsession — trying to write everything as pure functions tangles code where I/O and external comms live. "Push side effects to the boundary" is the practical line.
OOP obsession — modeling everything as a class. A 5-line empty class where a simple function would do.
Monad phobia — Haskell-style terminology is scary at first, but JS's Promise.then is also a monad shape. Start with syntactic familiarity.
Inconsistency from mixing — within one codebase, if the line between "where OOP, where FP" gets blurry, reading suffers. Team agreement is needed.
The flat assertion "this language is not OOP" — almost every modern language supports both. The difference is which style flows more naturally.
Closing thoughts
OOP and FP are no longer tribes but two seats in the same toolbox. Almost every modern language supports both, and real-world code mixes them naturally. UI and domain models → OOP; data transformation, validation, concurrency → FP. "Push side effects to the boundary" is the one line that combines the two views best.
Next
- (programming end)
We refer to Alan Kay The Early History of Smalltalk (1993) · Design Patterns: Elements of Reusable Object-Oriented Software (1994) · Effective Java (Joshua Bloch) · Why Functional Programming Matters (John Hughes 1990) · Structure and Interpretation of Computer Programs (SICP) · Haskell.org Learn · Out of the Tar Pit (Moseley · Marks 2006) · Composing Software (Eric Elliott) · Scala official · Rust Book · Kotlin Docs.