Programming is a blend of creativity and logic, where individuals shape their coding styles based on personal preferences. This often leads to debates about about two common programming paradigms: functional programming versus object-oriented programming (OOP). Which one is better? Which should you choose?

Each has unique strengths, and the choice between functional programming and OOP is context-dependent. This article provides an objective comparison, separating opinions from realities. You will come away with a better understanding of both approaches and be prepared to utilize them when it makes sense.

Functional programming overview

Functional programming (FP) is one of the oldest approaches to programming. It defines a process of building software that relies exclusively on pure functions.

In FP, developers compose functions to create new functions and write applications to avoid aspects such as shared state or mutable data. To achieve this, developers often use FP declaratively rather than imperatively.

So what does that all mean?

Key concepts in functional programming

As the name implies, functions are first-class citizens in FP. That means they can be treated like any other value, passed around as arguments or returned from other functions. A function that returns a function is called a higher-order function. These higher-order functions make it possible to compose new functions directly from arguments.

Immutability is key to FP. An immutable value is a primitive value, one that cannot change. Values like numbers are considered immutable. You cannot change 42 to 14. You can only create a new number with the value 14 and assign that to the previously used variable. But making that new number 14 does not affect the number 42 that you used previously.

While this kind of value treatment makes sense for primitives such as numbers, it may feel strange for composed values such as objects or arrays. Nevertheless, FP principles treat all values as immutable. The only way to change a value is to create a new one, potentially using an old one as a base value or copy.

The use of immutable data types allows FP to use pure functions. These functions are defined only by their arguments. Since the arguments cannot change, they are guaranteed to behave predictably. The same arguments give the same results. Other programming approaches do not guarantee this predictable behavior.

Use cases for functional programming

Although functional programming was originally used primarily in scientific applications, it has become increasingly common in a variety of fields.

For example, in web development, the React user interface (UI) library uses FP principles to become declarative and easy to handle. The library advocates using immutable state objects (values) to reflect the current state of the application without changing. When the developer wants a new state, they have to create a new object.

The beauty of this approach is that you can trace back each change in the UI. All previous states are still untouched and available.

Functional programming originated in computer science academia, with practical applications in languages such as Lisp and Scheme. Some of these traits were baked into the JavaScript language, which is one reason developers continue to rely on FP.

Other languages build on top of FP principles: F#, Clojure, and Elixir are among the more popular ones. In academia, Haskell has been considered the go-to language for decades.

Object-oriented programming overview

Functional programming has been around a long time, but some developers consider object-oriented programming (OOP) to be even more traditional. Object-oriented languages like Smalltalk or Objective-C popularized OOP, which was invented in the late 1970s. Later, C++, Java, and C# continued to hardwire this programming style into the minds of most developers.

Read on for descriptions of important principles in OOP and some of its most common uses cases.

Key concepts in object-oriented programming

In OOP, developers model software applications as collections of objects that can communicate with one another. The interface of each object is a class, a template that indicates functions and values are accessible to any instance.

Initially, developers intended this communication ability for sending messages for network communication or asynchronous IO. It was later popularized as simple functions invoked on the corresponding object. Such functions are called methods.

In contrast to the immutable objects in FP, in OOP object mutation is part of the game. So, calling a method will most likely also change some value of the object.

One reason many developers still use OOP, especially when teaching programming, is that it is imperatively written. This means the developer is explicit about what is happening where. Still, even using such an imperative style, it can be difficult to determine the current state of individual objects. Mutability means that this can quickly lead to unforeseen consequences.

Use cases for object-oriented programming

Traditionally, developers have made almost all UIs with an OOP approach. For example, a class-based component can just inherit its basic structure (fields and methods) from another similar component. For instance, a date input field can inherit from a text input field, which inherits from an input box, which inherits from a control.

Using this inheritance-based approach, you only need to specify the additional methods and reimplement the behavior on some existing methods. There is no need to write the logic for the keyboard or mouse handling again, for example.

Today, OOP is a must-have feature in all general-purpose programming languages. Even FP-based languages such as F# directly support OOP features, such as classes or inheritance. A good example is JavaScript, which did not incorporate standard features such as classes or inheritance right away but added them in more recent revisions.

As we have discovered, the two programming approaches are different enough to justify using both when applicable. So how do you choose between them? Let’s look at some some of the arguments.

Functional programming vs OOP: A comparison

Computer science professor Norman Ramsey offered a useful perspective on the functional programming vs OOP debate in a well-known Stack Overflow answer. He argued that FP excels when all objects are known but have behavior that may change. In contrast, OOP is great when the behaviors are known, but the actual data types may change.

Fans on either side will go beyond that. For instance, followers of FP argue that cleanly designed software written in a pure FP style is easy to debug and will never crash. They tell you that FP gives you some of the advantages of test-driven development right away, and that OOP with all SOLID principles rigorously applied is essentially FP.

While it is true that SOLID leads to individual functions resembling most of FP, it does not necessarily have to be FP. For instance, no SOLID principle forbids data mutation.

Developers who love OOP may dismiss some of the FP benefits for trade-offs like performance or simplicity. Why copy all of an object’s fields into a new object when you only want to change a single field? Why should an array with a million elements require a copy to set a single element? The level of indirection can be baffling compared to the direct approach developers have used independently of OOP.

While there are some pretty stark contrasts between these approaches, they can also be complementary. There is no law that forbids using classes in a software application. There is also no law saying that the application’s general state should be mutable.

The future of functional programming and OOP

Almost all popular programming languages are multiparadigm. They all support FP by allowing functions to be passed around or having helpers that deal with data object immutability. They also come with OOP features, such as classes or inheritance.

Either way, this multiparadigm approach is here to stay. From a user’s point of view, it is almost always better to have more choices.

In general, a tendency toward more FP-friendly features seems likely. By bringing in additional support for cloning data objects, function references, and function compositions, a language becomes a great companion, even beyond FP. In languages such as C#, the FP-friendly features provide an excellent addition to the OOP features. This combination comes in handy even in OOP-developed applications, like when you are using certain helper functions.

The downside of mixing FP and OOP approaches is the learning curve. Some people avoid C# for this reason. What started as a pristine, elegantly designed alternative to Java can end up feeling more like a complex monster along the lines of C++.

OOP won’t vanish, but may instead find itself coexisting with FP. The ideal of side-effect-free development is almost impossible to implement in practice. Even writing log messages to a console creates a side-effect.

Developers had first to discover and unlock the practical side of FP. Now that developers have started to recognize the practical side of FP, there is almost no reason to lock FP features out again.

Ongoing support for object-oriented programming

In this context, why should OOP still be a viable choice? OOP has value because it is essentially the ideal tool. There is almost nothing developers cannot model through OOP practices. Developers can even model most FP features through OOP.

For example, consider something as elementary as passing around or combining functions. In a nutshell, this is what a functor or delegate provides. It is just an object corresponding to a class with a single method.

Finding common ground

Very few development teams will change their software or way of writing applications just to switch from one approach to the other. It is more likely for a team to actively refactor some of their most essential applications to use more recent features of their programming languages.

Either way, the future looks increasingly hybrid. On some future project, you will probably find yourself using a more FP-inspired style in OOP applications or some OOP features in FP-driven applications.

Conclusion

When it comes to the debate between functional programming and OOP, the reality is that neither approach is inherently superior.

FP is often chosen for its simplicity and excellent performance in scenarios requiring high levels of concurrency or when a strong emphasis on stateless computation is needed. OOP, on the other hand, is preferred when the problem domain is best modeled with rich data structures and complex behaviors, or when the existing infrastructure and skill sets support an object-oriented approach.

Both paradigms have perks, so understanding them is crucial for making informed choices in line with project goals. Balancing personal preferences with project requirements will guide you to the right choice for you and your team.