I thought OOP was the only way to write Python โ€” until I discovered the power of FP.

I've written years of classes and methods, yet I learned that functional programming made my code cleaner, simpler, and more Pythonic. Here's what I learned.

As most developers, I began learning Python by going down the object-oriented programming (OOP) route. Every tutorial yelled at me: define a class, encapsulate your logic in methods, and instantiate objects.

And it was successful โ€” temporarily. I defined classes to every thing: user models, database wrapping, even a calculator app that likely required just a function or so.

But as my project expanded, I began to realize:

Too much abstraction made me slow down.

Boilerplate code clogged up my files.

Small utilities got too sophisticated.

That's when I chanced upon functional programming (FP). Initially, it was radical โ€” even "un-Pythonic." Yet the more I adopted it, the better I understood FP was as consistent as they come with Python's strengths.

This post is concerning the way I evolved from an OOP-dominant coding technique to a more functional way, how it impacted my development practice, and why I think that all Python programmers at least deserve a try at FP.

Why I Started Questioning OOP in Python

Object-oriented programming irrevocably has some strengths. It excels when:

You are to simulate big entities (such as a bank account, a user account, or a character in a game).

You desire good encapsulation of state and behavior.

You're coding in big groups that depend on entrenched molds such as inheritance and polymorphism.

But in Python, OOP can easily turn into overengineering. Consider this overly "classy" snippet I once wrote:

class Calculator:
    def add(self, a, b):
        return a + b
    
    def multiply(self, a, b):
        return a * b

To use it, I'd write:

calc = Calculator()
result = calc.add(2, 3)

Why? What was the value of the class added here? A mere function would have been neater:

def add(a, b):
    return a + b

def multiply(a, b):
    return a * b

I discovered I was authoring classes because I thought I needed them, not because I really needed them.

The Turning Point: Discovering FP's Simplicity

Functional programming is not about getting rid of OOP in totality โ€” it's all about attitude:

Functions are first-class citizens.

Data cannot be changed by default.

Character is more important than birth.

Side effects must be minimal.

FP in Python typically involves relying upon intrinsics such as map, filter, and reduce, as well as higher-order functions and expressive comprehensions.

Here's an actual example based on a project where I needed to process the user's information. My previous OOP way was as follows:

class UserProcessor:
    def __init__(self, users):
        self.users = users
    
    def get_active_usernames(self):
        return [u["username"] for u in self.users if u["active"]]

My FP-inspired rewrite:

def get_active_usernames(users):
    return [u["username"] for u in users if u["active"]]

Both work. But one is simpler, clearer, and doesn't need a whole class.

5 Ways FP Improved My Python Code

1. Less Boilerplate, More Focus

I release less text to the wild. Methods do not require constructors, self arguments, or object creation.

2. Easier Testing

Pure function is predictable: same in, same out. No subtle state, no surprises.

def square(x): 
    return x * x

Testing is as simple as:

assert square(3) == 9

No setup, no teardown, no mocks.

3. Composability

Rather than creating intricate class hierarchies, I construct pipelines of functions.

Example:

from functools import reduce

numbers = [1, 2, 3, 4, 5]
total = reduce(lambda x, y: x + y, filter(lambda n: n % 2 == 0, numbers))

This reads: filter even numbers โ†’ reduce them by summing โ†’ done.

4. More Pythonic

"Simple is better than complex." List comprehensions, generators, and higher-order functions come as no surprise having adopted FP.

5. Better Parallelization

As functional code doesn't share state, it is simpler to parallelize using libraries such as concurrent.futures or multiprocessing.

When I Still Use OOP

I've yet to give up OOP completely. There are still situations where classes excel:

Frameworks wait for them (Flask views, FastAPI routers, Django models).

Stateful systems such as GUI programs or games.

Reusable abstractions where encapsulation enhances clarity.

The key is: I now use OOP deliberately, not by default.

Practical Tips to Embrace FP in Python

If you're wonderin' how to try FP in your own projects, these steps helped me:

1. Begin modest โ€” swap unnecessary classes for standalone functions.

2. Take advantage of built-ins โ€” get lots of practice in using map

3. Consider immutably โ€” favor tuples, frozenset, and refrain from mutating in

4. Practice libraries โ€” look at toolz, funcy, or returns for FP utils.

5. Refactor slowly โ€” don't reimplement the entire thing all at once. Implement FP concepts where they apply.

Conclusion: The Joy of Simpler Code

Converting OOP-first to FP-first in Python wasn't only a technical change โ€” it was a mentality change. My code received:

Shorter

Easier to read

More testable

More Pythonic

And most importantly, I guess: I began enjoying coding more.

If you're drowning in boilerplate and classes, give writing your next function wihout a class wrapper a try. You can, as I did, end up feeling Python is even more monumental when OOP isn't forced where it's not needed.

Don't stick to the same paradigm. Allow the problem to dictate the style โ€” not vice versa.