Traditional programming languages, such as C, FORTRAN, Pascall, are based on procedural programming. This programming paradigm uses procedure calls, where each procedure (e.g. function or subroutine) is a set of computational steps to follow. It is accessible and easy to understand, but when code gets longer or more complex, making changes to one function can cause a cascade of bugs that can be a headache to trace back. To avoid this spaghetti code, computer architects formulated two relatively newer programming paradigms: object-oriented programming (OOP) and functional programming.
Each paradigm takes a very different approach to how code is structured. In this article, we’ll break down what exactly each programming paradigm is and what it’s typically used for. Hopefully by the end you will have started to form some opinions of your own. Let’s get started!
Functional programming is centered around building software composed of functions, similar to procedural programming; however, there are subtle differences. We highlight these differences by discussing key concepts of functional programming below.
Functions are treated as primitives. Primitives serve as the simplest elements of a programming language. Thus, a function can be:
Since they have these capabilities, functions are referred to as first-class citizens. The term first-class citizen essentially notates an element that supports all other generally available operations.
A goal of functional programming is to write pure functions whenever possible.
A pure function does not change the global state of the program and therefore is said to have no side effects. Instead, its return value is solely based on its input parameters.
Pure functions enable numerous compiler optimizations, leading to significantly reducing a program’s execution time. Furthermore, pure functions are ideally suited for unit testing. We can unit-test a pure function in isolation without having to worry about where in the code it will be used.
Instead of iterative loop constructs, such as
while, in functional programming, we use higher-order functions and tail calls for a recursive loop.
A higher-order function takes a function as its input, performs an operation on the input function, and subsequently returns a function.
A tail call refers to a function call at the end of a function. A tail call may result in a recursive loop when a function calls itself. Many compilers perform tail call optimization (a.k.a. tail call elimination) making recursion as fast as iterative loops.
Key reasons for preferring recursion over typical primitive loops are:
Immutability is the permanence of a variable once the data is assigned to it.
No value assigned to a variable in functional programming can be changed; the only way to change a value is to create a new variable. It is immutability that enables functional programming to use pure functions. These functions do not mutate their inputs and rely on nothing but the input to achieve results. In this way, functional programming is similar to math. Functional programming was even created for an academic computer science setting through the Haskell language.
In summation, functional programming is a declarative programming model that communicates what the program does without specifying the flow of control. It uses pure functions, immutable variables, and tail-call recursion. This results in code that is easier to understand, debug, and unit-test. Furthermore, functional programming enables numerous compiler optimizations for an increase in a program execution speed and reduction in memory requirements.
Try one of our 300+ courses and learning paths: Learn Functional Programming in Python.
In contrast to functional programming, object-oriented programming (OOP) is an imperative paradigm. This means that the code describes how the program should achieve a result in a step-by-step process. These statements procedurally change the program’s state.
The term state, in OOP, refers to any set values plus the alterations made to them over the course of the program.
Object-oriented programming groups related functions and their variables into objects. In an object, functions are referred to as methods and variables are referred to as properties.
For example, this figure represents how OOP may represent a car as a group of properties and methods.
A common question you may see in interviews is to identify the four pillars of OOP. The four pillars are encapsulation, abstraction, inheritance, and polymorphism. It is important to recognize these tenets when studying object-oriented programming as they are all interconnected.
Encapsulation: the act of grouping related variables and functions into objects. The creation of an object is based on a blueprint or template called a class.
Grouping functions and variables allows for functions to be called with few to no parameters, as the variables are incorporated as a part of the object.
Abstraction: hiding some of an object’s properties and methods from outside observation.
Confining certain parameters and functions to an object ensures that any future changes made to the abstracted members of a class will have no impact on the rest of the classes. Thus, making extension and improvement of a program easier.
Inheritance: an object can inherit some of the properties and methods of another object.
In Java, this inheritance is notated by the keyword extend. The parent class from which another class is extending is called a superclass or a base class. For instance, the
Ford classes may be extensions of the
Car class. Whereas,
Ford Mustang is an extension of the
Ford class. In this case, common code to both
Ford can be written once in their superclass
Thus, inheriting existing properties and methods, and adding new features to them, reduces code redundancy and increases reusability.
Polymorphism: means literally “many forms,” and refers to objects that respond in different ways to the same function.
Polymorphism is based on an object’s relationships with other objects and how members of a superclass are being extended in its derived classes. This again reduces redundancy and increases reusability. Polymorphism is a complex concept; if you are interested in learning more, we encourage you to check out the course: Learn Object-oriented programming in Java.
Each pillar of OOP is important in its own right, but the pillars are dependent on one another. Encapsulation is a necessary feature for abstraction and inheritance to be possible. Additionally, polymorphism cannot exist without inheritance. Each of the four pillars is integral to the paradigm. OOP as a whole functions through objects with self-contained properties and methods, and their relationships with other objects.
Try one of our 300+ courses and learning paths: Learn Object-Oriented Programming in Python.
Some programming languages only allow for one particular paradigm. For example, Haskell is by definition a functional programming language because its state cannot mutate. Other primarily functional programming languages include Lisp, F#, and Erlang. Some OOP languages, or languages that lean more toward the object-oriented paradigm are Java, C#, and C++. Several programming languages, such as TypeScript and Python, are multi-paradigm and support both OOP and functional programming to some capacity. These languages have certain allowances to utilize pure functions, and immutable objects at the developer’s will.
Python is one of the most popular languages, and it represents a middle-ground on this spectrum of mutable to immutable or object-oriented to functional. Python has built-in types that are immutable: strings, numbers, and Booleans, to name a few. Custom classes in Python, however, are typically mutable data. With a language like Python, it is almost entirely up to the programmer as to what paradigm they use primarily.
OOP is currently the paradigm used by most developers, mostly for its accessibility. Functional programming is great for the server side of applications, data manipulation, and web crawling. For example, all of Facebook’s spam filtering was built using functional programming in Haskell. For the most part, there is no clear-cut answer as to what paradigm will serve you best in your software development education and career. Good software engineers use techniques and habits that can be prescribed to both object-oriented programming and functional programming.
Even if you primarily code in an OOP environment it can benefit you to implement some functional programming constraints. In doing so, you may find that your code is clearer and easier to debug. One of the biggest barriers to making an effective, easy-to-use API is repetitive code, so learning and practicing functional programming concepts now may pay dividends in the future.
When deciding what paradigm to develop a new program in, it can be beneficial to create a roadmap for how the program may change in the future. If your program has a set number of operations to be performed on entities, and you intend to grow the application by adding more entities, an object-oriented approach makes the most sense. If instead, you have a fixed number of entities and intend to add more operations, functional programming will create fewer problems during scaling.
At its simplest, functional programming uses immutable data to tell the program exactly what to do. Object-oriented programming tells the program how to achieve results through objects altering the program’s state. Both paradigms can be used to create elegant code. From an OOP perspective you may be sculpting something out of clay, modeling it after the real world. Through functional programming it more closely resembles stacking building blocks together to eventually create a cohesive piece.
So where should you go from here? If you want help acing an object-oriented programming job interview you can check out the course: Grokking the Object Oriented Design Interview
Join a community of more than 1.4 million readers. A free, bi-monthly email with a roundup of Educative's top articles and coding tips.