Object-oriented programming organises a program around things that hold state and expose operations on it. Functional programming organises it around transformations of data that never change their inputs. Most modern languages support both, most real programs mix them, and the useful question is not which paradigm is better but which one fits the part of the program you are writing.
The two ideas#
OOP: group data with the methods that operate on it into objects, hide the internal details, and let objects change over time through those methods.
FP: write functions that take inputs and return outputs without changing anything else, keep data immutable, and build programs by composing those functions.
Neither is about syntax. A language with classes can be written functionally; a language with first-class functions can be written in an object style. It is about where state lives and who is allowed to change it.
The same problem, both ways#
A shopping basket: add items, apply a discount, get the total.
# Object-oriented: the basket holds state and changes it
class Basket:
def __init__(self):
self.items = []
self.discount = 0.0
def add(self, name, price):
self.items.append((name, price))
def apply_discount(self, fraction):
self.discount = fraction
def total(self):
subtotal = sum(price for _, price in self.items)
return subtotal * (1 - self.discount)
basket = Basket()
basket.add("book", 12.0)
basket.add("pen", 3.0)
basket.apply_discount(0.1)
print(basket.total()) # 13.5
# Functional: data is plain, functions return new data
def add(items, name, price):
return items + [(name, price)] # a new list; the old one is untouched
def total(items, discount=0.0):
subtotal = sum(price for _, price in items)
return subtotal * (1 - discount)
items = []
items = add(items, "book", 12.0)
items = add(items, "pen", 3.0)
print(total(items, discount=0.1)) # 13.5
Both work. The OOP version has one thing, the basket, that you talk to and that remembers. The FP version has data that flows through functions, and at any moment you can hold the old version and the new version side by side.
What immutability buys#
In the functional version, add cannot surprise anyone: given the same inputs it returns the same output and touches nothing else. That property, being pure, has consequences:
- Testing is trivial. Call the function, check the return value. No setup, no teardown, no “what state was the object in”.
- Concurrency is safe by default. Two threads cannot corrupt data that nothing changes.
- Reasoning is local. To understand a call, read the function. You never need to know what happened earlier.
- Undo and history are free. Keep the old value; it is still valid.
The cost is that “change” means building a new value, which is more work to write and sometimes more work for the machine, though languages designed for it make both cheap.
What objects buy#
- A natural model for things with identity. A user account, a network connection, a game character: they exist over time and change. An object matches that directly.
- Encapsulation. The rest of the program cannot put the object into an invalid state, because only its methods can touch the internals.
- Polymorphism. Different kinds of thing responding to the same message —
shape.area()for a circle or a square — without the caller caring which. - Familiarity. Most frameworks, libraries and teams are organised this way.
The cost is that state spread across many mutable objects becomes hard to reason about, and the bugs that result — something changed it, but what? — are the expensive kind.
Where each fits#
| Part of the program | Leans | Why |
|---|---|---|
| Data transformation, pipelines, calculations | FP | Inputs to outputs, nothing to remember |
| Long-lived things: sessions, connections, UI widgets | OOP | Identity and lifecycle matter |
| Business rules | FP | Pure rules are testable and composable |
| Frameworks and plugins | OOP | Interfaces and polymorphism define extension points |
| Concurrency | FP | Immutable data needs no locks |
| Simulation and games | Mixed | Entities are objects; update rules are often functions |
How they combine#
The pattern most experienced developers settle on: objects at the edges, functions in the middle. Objects manage the things that genuinely have state — connections, the application itself, the UI — and they call pure functions to do the actual work. Data is passed as plain immutable values; a class is used when something needs identity or an interface.
# A class with state, delegating the logic to pure functions
class OrderService:
def __init__(self, repository):
self.repository = repository # a connection: legitimately stateful
def checkout(self, basket_items, discount):
amount = total(basket_items, discount) # pure function: easy to test alone
order = build_order(basket_items, amount)
self.repository.save(order) # the one side effect, at the edge
return order
Python, JavaScript, C#, Java, Kotlin and Swift all support this mix comfortably. Languages such as Haskell and Elixir push you towards the functional end; Java and C# historically pushed towards objects but have absorbed a great deal of FP since.
Questions people ask#
Is one paradigm faster?
Not inherently. Immutable data can cost allocations; mutable shared state can cost locks. Performance depends on the language and the workload far more than the paradigm.
Should a beginner learn OOP or FP first?
Learn functions first, because everything uses them; then classes, because most code you will read uses them. The paradigm labels can wait until both feel natural.
Is FP only for maths-heavy code?
No. Web request handling, data cleaning, configuration and business rules are all naturally functional: input in, output out.
What are closures and higher-order functions?
Functions that take or return other functions, and functions that remember the variables around them. They are the FP tools for composition, and both exist in every language mentioned here.
Where to go next#
- Functions and functional programming — the FP side in depth.
- OOP in Python — the object side, with code.
- Big O notation — the other fundamentals topic everyone skips.