I think OOP became popular because it feels profound when you first grasp it. There is that euphoric moment when all the abstractions suddenly interlock, when inheritance, polymorphism, and encapsulation seem to dance together in perfect logic. It feels like you have entered a secret order of thinkers who understand something hidden. Each design pattern becomes a small enlightenment, a moment of realization that the system is clever in ways that ordinary code is not.
But if you step back far enough, the brilliance starts to look like ornament. Many of these patterns exist only to patch over the cracks in the paradigm itself. OOP is not a natural way of thinking, but a habit of thinking that bends reality into classes and hierarchies whether or not they belong there. It is not that OOP is wrong, but that it makes you mistake complexity for depth.
Then you encounter functional programming, and the same transformation begins again. It feels mind expanding at first, with the purity of immutable data, the beauty of composability, and the comfort of mathematical certainty. You trade one set of rituals for another: monads instead of patterns, recursion instead of loops, composition instead of inheritance. You feel that familiar rush of clarity, the sense that you have seen through the surface and reached the essence.
But this time the shift cuts deeper. The difference between the two paradigms is not just structural but philosophical. OOP organizes the world by binding behavior to state. A method belongs to an object, and that object carries with it an evolving identity. Once a method mutates state, it becomes tied to that state and to everything else that mutates it. The entire program becomes a web of hidden dependencies where touching one corner ripples through the whole. Over time you code yourself into a wall. Refactoring stops being a creative act and turns into damage control.
Functional programming severs that chain. It refuses to bind behavior to mutable state. Statelessness is its quiet revolution. It means that a function’s meaning depends only on its inputs and outputs. Nothing else. Such a function is predictable, transparent, and portable. It can be lifted out of one context and placed into another without consequence. The function becomes the fundamental atom of computation, the smallest truly modular unit in existence.
That changes everything. In functional programming, you stop thinking in terms of objects with responsibilities and start thinking in terms of transformations that can be freely composed. The program stops feeling like a fortress of interlocking rooms and begins to feel like a box of Lego bricks. Each function is a block, self-contained, perfectly shaped, designed to fit with others in infinitely many ways. You do not construct monoliths; you compose arrangements. When you need to change something, you do not tear down the wall. You simply reassemble the bricks into new forms.
This is the heart of functional nirvana: the dream of a codebase that can be reorganized endlessly without decay. Where every part is both independent and harmonious, where change feels like play instead of repair. Most programmers spend their careers trying to reach that state, that perfect organization where everything fits together, but OOP leads them into walls that cannot move. Functional programming leads them into open space, where everything can move.
Reality will always be mutable, but the beauty of functional programming is that it isolates that mutability at the edges. The pure core remains untouched, composed of functions that never lie and never change. Inside that core, every function is both a truth and a tool, as interchangeable as Lego bricks and as stable as mathematics.
So when we ask which paradigm handles complexity better, the answer becomes clear. OOP hides complexity behind walls. Functional programming dissolves it into parts so small and transparent that complexity itself becomes optional. The goal is not purity for its own sake, but freedom; the freedom to recompose, reorganize, and rethink without fear of collapse. That is the real enlightenment: when your code stops feeling like a structure you maintain and starts feeling like a universe you can endlessly reshape.
The great achievement of OOP is that it inspires such passion.
In essence OOP is just, "hey, if you have a struct and a bunch of operations that operate on that struct, let's put the name of the struct and a dot in front of the names of those operations and you don't need to pass the struct itself as an argument"
It beats me how either the high priests or its detractors get so worked up about it, even with the add-ons like inheritance, poly-morphism or patterns. (Which of course also exist in a more mathematically clean way in functional languages.)
These patterns have seen real use (not saying optimal) in the wild.
Of course we know today composition is better than inheritance, plain data structs are enough for most cases, and "parse, don't validate". but did people know it in 1990s?
You’re missing the depth of the difference. It’s not just syntax sugar for calling object.method() instead of func(object). The key distinction is what happens when the method mutates the object.
When state is mutable, every method that touches it becomes coupled to every other method that touches it. The object stops being a collection of independent behaviors and turns into a shared ecosystem of side effects. Once you mutate state, all the code that relies on that state is now bound together. The object becomes a single, indivisible unit. You cannot take one method and move it elsewhere without dragging the rest of its world along with it.
Functional programming avoids that trap. Functions are isolated. They take input and return output. They don’t secretly reach into a shared pile of state that everything else depends on. That separation is not aesthetic, it is structural. It’s what makes functions genuinely modular. You can pull them out, test them, recombine them, and nothing else breaks.
# OOP version
class Counter:
def __init__(self):
self.value = 0
def increment(self, n):
self.value += n
def double(self):
self.value *= 2
c = Counter()
c.increment(5)
c.double()
print(c.value)
Here, every method is bound to self.value. Change how one works and you risk breaking the others. They share a hidden dependency on mutable state.
Now compare that to the functional version:
def increment(value, n):
return value + n
def double(value):
return value * 2
increment_and_double = lambda x: double(increment(x, 5))
print(increment_and_double(0))
In this version, increment and double are completely independent. You can test them, reuse them, and combine them however you like. They have no shared state, no implicit dependency, no hidden linkage.
People often think OOP and FP are complementary styles. They are not. They are oppositional at the core. OOP is built on mutation and shared context. FP is built on immutability and isolation. One binds everything together, the other separates everything cleanly.
Mutation is what breaks modularity. Every time you let a method change shared state, you weave a thread that ties the system tighter. Over time those threads form knots, and those knots are what make change painful. OOP is built around getters and setters, around mutating values inside hidden containers. That’s not structure. It’s coupling disguised as design.
Functional programming escapes that. It separates state from behavior and turns change into a controlled flow. It makes logic transparent and free. It’s not just another way to code. It’s the only way to make code truly modular.
You can write these same methods in an OOP language like Java. You dont have to use classes for everything.
But alot of times, yes it makes sense to group a set of related methods and states.
You say this is not a natural way of thinking but I strongly disagree, it lines up perfectly with how I think. You are you, the car dealership is a dealership. You buy a car from the car dealership, the dealership gets money, you lose money, the dealership loses a car and you gain a car. I want these states reflected in the objects they belong and not passed around globally and tracked
Or if I am writing an API library, yes I very much want to 1. group all my calls together in a class, and 2. keep track of some state, like auth tokens, expirations, configuration for the http client, etc. So you can just do api.login, api.likeX, etc
Moreover, most methods youd write in a large project are so limited in scope to the type and purpose, this idea of some great modularity is nonsense. Its not as if you can have a single delete function that works on deleting users, images from your s3, etc. Youd end up writing bunch of functions like deleteUser(user), createUser(user), deleteImage(image), and wow wouldn't it be great if we could just group these functions together and just do user.delete, user.create? we could even define an interface like Crudable and implement it differently based on what we're deleting. wows
Ok simple question. Have a method called increment(n) on an object called tracker. It’s used to increment tracking id values.
I want to reuse the logic of increment(n) inside another object called childHeight to increment height values.
Can I import the method and reuse it inside another object? No. I can’t.
But if increment was a pure function thats like this increment(c, n) then I can move it anywhere.
That is what I mean by modularity. For oop lack of modularity is fundamental to its design. The grouping of methods around mutating state breaks modularity.
You’re talking about a semantic issue. Grouping methods by meaning. I’m talking about a logistical issue where the grouping by semantics cannot be broken even though the logic is the same. Incrementing height and incrementing ids are identical in logic even though the semantics are divergent.
class Number { //or extend the Integer class and add your methods
int num = 0;
inc() {
this.num +=1
}
}
class Tracker {
Number trackingId;
}
class Building {
Number height;
}
t = Tracker()
t.height.inc()
b = Building()
b.height.inc()
All OOP does is really give you a way to group state and methods, how you use it is up to you. There is a reason almost all big software is written this way and not in Lisp.
Do you see the problem? What you wrote is the problem with oop. Gotta tear your whole program apart and rewrite it. You had to create new things and rewrite your methods.
With functional you don’t rewrite. You recompose what you already have.
It means oop is not modular. You didn’t create modules that can be reused. Nothing could be reused so you had to reconfigure everything.
There's a video from Gary Bernhardt called Functional Core, Imperative Shell which made sense to me. These paradigms can work well together. OOP can be great too, the Ruby language is deeply OO and the core library is rich and OO.
You can also only use hashes and arrays and type them with Typescript, using functions, types and namespaces and duck-typing, instead of classes and methods.
ps: closures are worth thinking about, we use them without even thinking about it
I want to believe you, but some of this verbage sounds very strange, as if you're don draper trying to sell me functional programming. There are a lot of stinks within those paragraphs. I have no proof that you did, but something about it feels off.
There’s no need to respond then. No point in this accusation either. You make an accusation, I deny it, life goes on and nothing is different except a little wasted effort. Save the effort.
I think OOP became popular because it feels profound when you first grasp it. There is that euphoric moment when all the abstractions suddenly interlock, when inheritance, polymorphism, and encapsulation seem to dance together in perfect logic. It feels like you have entered a secret order of thinkers who understand something hidden. Each design pattern becomes a small enlightenment, a moment of realization that the system is clever in ways that ordinary code is not.
But if you step back far enough, the brilliance starts to look like ornament. Many of these patterns exist only to patch over the cracks in the paradigm itself. OOP is not a natural way of thinking, but a habit of thinking that bends reality into classes and hierarchies whether or not they belong there. It is not that OOP is wrong, but that it makes you mistake complexity for depth.
Then you encounter functional programming, and the same transformation begins again. It feels mind expanding at first, with the purity of immutable data, the beauty of composability, and the comfort of mathematical certainty. You trade one set of rituals for another: monads instead of patterns, recursion instead of loops, composition instead of inheritance. You feel that familiar rush of clarity, the sense that you have seen through the surface and reached the essence.
But this time the shift cuts deeper. The difference between the two paradigms is not just structural but philosophical. OOP organizes the world by binding behavior to state. A method belongs to an object, and that object carries with it an evolving identity. Once a method mutates state, it becomes tied to that state and to everything else that mutates it. The entire program becomes a web of hidden dependencies where touching one corner ripples through the whole. Over time you code yourself into a wall. Refactoring stops being a creative act and turns into damage control.
Functional programming severs that chain. It refuses to bind behavior to mutable state. Statelessness is its quiet revolution. It means that a function’s meaning depends only on its inputs and outputs. Nothing else. Such a function is predictable, transparent, and portable. It can be lifted out of one context and placed into another without consequence. The function becomes the fundamental atom of computation, the smallest truly modular unit in existence.
That changes everything. In functional programming, you stop thinking in terms of objects with responsibilities and start thinking in terms of transformations that can be freely composed. The program stops feeling like a fortress of interlocking rooms and begins to feel like a box of Lego bricks. Each function is a block, self-contained, perfectly shaped, designed to fit with others in infinitely many ways. You do not construct monoliths; you compose arrangements. When you need to change something, you do not tear down the wall. You simply reassemble the bricks into new forms.
This is the heart of functional nirvana: the dream of a codebase that can be reorganized endlessly without decay. Where every part is both independent and harmonious, where change feels like play instead of repair. Most programmers spend their careers trying to reach that state, that perfect organization where everything fits together, but OOP leads them into walls that cannot move. Functional programming leads them into open space, where everything can move.
Reality will always be mutable, but the beauty of functional programming is that it isolates that mutability at the edges. The pure core remains untouched, composed of functions that never lie and never change. Inside that core, every function is both a truth and a tool, as interchangeable as Lego bricks and as stable as mathematics.
So when we ask which paradigm handles complexity better, the answer becomes clear. OOP hides complexity behind walls. Functional programming dissolves it into parts so small and transparent that complexity itself becomes optional. The goal is not purity for its own sake, but freedom; the freedom to recompose, reorganize, and rethink without fear of collapse. That is the real enlightenment: when your code stops feeling like a structure you maintain and starts feeling like a universe you can endlessly reshape.