4 Programming Paradigms Every Developer Should Know

4 Programming Paradigms Every Developer Should Know

It’s important to know them even if you’re not using them

Featured on Hashnode

I’m a firm believer that if you’re a developer, you need to be aware of how things work outside of your comfort zone, even if you’re not planning to step outside of it anytime soon. The fact that you know there is a different way of doing something is by itself opening up your horizon to new methodologies and technologies.

Of course, you then have to try them, otherwise, you’re not really doing much, however, the jump required to go from “reading” to “trying it” is actually not that big, so consider giving, at least, one of the 4 programming paradigms I’ll be covering here a try. You’ll probably learn a thing or two while doing it and if you happen to be using a language that is compatible with more than one of these, you don’t even have to go that far to try them.

So let’s get our hands dirty!

Procedural Programming

Most developers start by this one even when they don’t know it by name. However, once they start learning about paradigms, they confuse this one quite often with Functional programming, because after all, most modern programming languages have renamed the word “procedure” to “function”. This causes a lot of awkward moments during tech interviews when the dev is asked about FP and they claim they’ve been using it for years, but at the same time, are unable to answer questions such as “what’s a pure function?” or “what’s a HOF?

Procedural programming is nothing more than working with procedures (a.k.a functions) and regular data structures. We’re not talking about “Functional programming” here, not yet, because this paradigm doesn’t deal with some of the concepts (such as Higher Order Functions) that make FP, well… FP.

So what’s so special about PP?

(hehe… I’m a simple man — and a child — , I know, but I read “pp” and I laugh).

It’s easy to learn, that’s — in my opinion — why most developers (both self-taught and coming from places like Universities) tend to learn first. You don’t need to understand a lot of complex concepts to pick it up. Once you’ve understood what are the basic types, control flows and conditional expressions, the next logical step is to understand how to group those lines of code into something that can be called multiple times from different places. In other words: a procedure.

Many languages allow you to work this way by not enforcing a specific paradigm onto you: JavaScript, Python, PHP are just 3 examples of this.

Is it a good paradigm to pick up at the start of your career? Heck yes! It has two major characteristics that make it a perfect first choice: it’s simple and it works for anything you want to do.

The downside of Procedural Programming

Is it a good paradigm to use as part of a large team of developers working on a big product? Maybe not ideal. And hear me out, maybe you’re building great software with it, but you’re either using concepts from other paradigms to extend this one, or you’re not taking advantage of the benefits from other, more complex paradigms.

The main problem I have with PP, is that it’s over-simplistic:

  • You’re not able to logically group state and behavior unless you count having modules, but even so, if you’re using them to simulate classes and objects, then you’re borrowing concepts from other paradigms, you’re not staying within the confines of PP.
  • Not having access to some of the FP concepts, forces you to write imperative code, which is somewhat harder to mentally parse and understand than declarative code. In other words, instead of telling the compiler/parser what you want to happen once your code is executed, you have to tell it how it needs to happen. It’s the difference between having to say “for each element inside this list of elements, please go one by one, multiply them by two and store the result at the end of this new list I just created” (imperative) versus having to say “store the result of mapping each element of this list into this new variable I just created” (declarative).
  • It lends itself to some classic bad practices such as abusing global variables because it doesn’t provide elegant alternatives. Granted, you can pass attributes from function call to function call, but if you have many nested calls that can end up with you having parameters from the global context drilling down function calls simply so they can be used 3 levels down.

For example:

const myName = "Fernando"

function startProcess(name) {
  //...bunch of logic

  secondStageOfProcess(name);
}

function secondStateOfProcess(name) {
  //...bunch of more logic
  startThinkingAboutGreetingUser(name)
}

function startThinkingAboutGreetingUser(name) {
  //...even more logic here
  sayHi(name)
}

function sayHi(name) {
  console.log("Hi there", name, "!")
}

startProcess(myName)

That code there is terrible, because we’re having to pass the myName constant through 3 different functions before I can reach the bit of logic that actually needed it. This creates a lot of clutter in the code and makes it both, hard to read and hard to maintain.

Let’s take a look at some alternatives that expand on the concepts of PP to create a better developer experience.

Tip: Build anything from independent components

Say goodbye to monolithic applications and leave behind you the tears of their development.

The future is components; modular software that is faster, more scalable, and simpler to build together. OSS Tools like Bit offer a great developer experience for building independent components and composing applications. Many teams start by building their Design Systems or Micro Frontends, through shared components. Give it a try →

An independently source-controlled and shared “card” component. On the right => its dependency graph, auto-generated by Bit.

Functional Programming

FP is often a very misunderstood paradigm simply because of how it is covered, mostly, on formal education. You see, a lot of developers going through college learn about FP through languages such as Haskell and Clojure. And the example use cases that they cover are sometimes very math-oriented.

Don’t get me wrong, that’s a very purist way of learning and if you’re lucky enough and you keep researching about the topic, you’ll find out that FP can actually be used in many real-world applications and that it also makes things a lot simpler.

However, that is not the case, many developers don’t know that they have a JVM-based language called Scala that they can use, or that JavaScript supports many of the main concepts of FP out of the box.

What’s cool about FP?

One of the main points in favor of FP, especially if you ask me, is that you can write very declarative code easily.

You can come very close to be telling a story of what your business logic is meant to be doing, without having to get into the nasty implementation details.

//Getting the list of invoices to pay for this month

let myInvoices = filterOutPaidOnes(
  filterByThisMonth(
   getInvoices()
  )
)

//..or perhaps something like this
let myInvoices = getInvoices(
  filterByCurrentMonth(
    filterOutPaidOnes
  )
)

Either one of the examples above leaves very little to interpretation or rather, you don’t need to understand the language to understand the logic. The first one has a bit more of a math-type approach, it’s very similar to f(g(x)) where you first need g(x) to then call the function f . However, given the names on the code, you can also figure that part quite quickly.

There is also the fact that because in FP we’re dealing with the concept of “pure function” (a function that has no side-effects and that returns the same output every time it gets executed with the same input), which simplifies tasks such as:

  • Unit testing. It’s a lot easier to test something that you know can’t possibly affect anything outside of its direct domain.
  • It’s a lot more secure to use, considering that there are no unforeseen side-effects (i.e by calling a function, you’re not accidentally changing a global variable).
  • Pure functions’ signatures are a lot more meaningful than normal function signatures. This is mainly because normal (or rather impure) functions don’t have to follow a strict contract, they can grab data from outside (even from the global scope) and its output is equally unrestricted. Given how pure functions work, they don’t have that luxury, so the signature of the function is key to determine what data they have access to and what (if anything) they’re going to return.

Is there a downside to FP?

The main downside I see is the way Functional Programming is normally taught. Students usually miss the fact that this is a very useful methodology because of the type of problems they’re taught to solve with it.

In fact, very few of the languages used are normally seen outside of educational contexts (when was the last time you’ve been a Haskell program running in the wild?). Meaning that even if they wanted to, it’s hard for them to even extrapolate the knowledge on their own, because they don’t even have the right tools to begin with.

If instead, they would also be shown more practical use cases, such as including and mixing functional programming with other paradigms, as Python and JavaScript do, then they would quickly pick up on the benefits and they’d fall in love with it a lot faster.

Notice though that I said “also shown” and not “instead of”. I don’t think Haskel, Clojure, or any of the other languages used to formally teach FP should not be used. However, the “purist” approach that is normally taken is the one that might benefit from taking a more practical route.

Logical Programming

Here is a curveball if you’ve ever seen one in our industry. Logical programming breaks the mold in every aspect when it comes to dealing with problem-solving.

If you missed objects on FP, you’ll miss everything with this one, trust me.

Logical programming is another paradigm very rooted in Math and — obviously— logic. Instead of dealing with code, loops and conditional sentences, it deals with Facts and Rules. Let me explain.

In the world of LP, when you want to solve a problem that solution takes the form of the answer to a question. And for you to be able to ask the question, you first need to build the world around it (its context).

So you start by stating some facts, which are essentially true things about entities. For example:

Einstein **is a scientist**

Fantastic, that’s our fact right there. No one will question that fact.

Second we start writing the rules, which are inferences about our domain (in this case, about scientists):

All scientists **are smart**

And finally, our question, which is what we wanted to know from the beginning:

Is Einstein smart?

The answer to our question, based on our facts and rules, will of course, be yes . And while this example is silly and oversimplistic, it also shows the incredible power that Logical Programming has. Because I did not write a single line of code, not the code we’re used to writing anyway.

I did not have to set up a list of characteristics of smart people, or I did not write an IF statement anywhere. Honestly, I was just focusing directly on the problem at hand, the low-level stuff was inferred by the interpreter, cool isn’t it?

One of the main LP languages out there is Prolog, and while you might’ve never heard of it before, it’s used in very niche fields inside some industries.

Is there a downside to Prolog?

Yes, there is, but it doesn’t make it useless, it just makes it unknown to most of our community of developers. The main downside is that it works so well at solving very few and very limited set of problems, that most developers (who have never had to face such problems) don’t know about it.

In fact, many developers delving in AI for example, which is one of the main industries where it’s being used right now, use other classical approaches because they’ve never heard of it. That’s how niche LP is!

The other problem with LP is that it’s so different from the way we’re used to code, that honestly considering it as an actual solution to one of our problems becomes a leap of faith. Until you’ve tried it and played around with it long enough, you won’t really be able to understand if this is the right tool for the job. And even if you think you see the potential of LP, making the jump and using it will require a lot of practice and headaches.

Don’t get me wrong, if you’re considering LP, go for it, give it an honest test drive. But make sure you understand that picking up Prolog or any other LP alternative is not like going from PP to FP, the distances are longer and the mental model switch will be bigger.

Object-Oriented Programming

Finally, OOP is the last paradigm I wanted to cover. Most of you probably know about and have been using it for years, but I’m sure others don’t and that’s why I thought I’d also cover it here.

Maybe you’re just getting started and you’re using a language that doesn’t enforce OOP but allows you to dip your toes into these object-infested waters. That’s a great way to get started, because you can slowly start incorporating more and more into a procedural environment.

So what’s the deal with OOP? Well, in the OOP world you take a very material approach to representing your problem universe through concepts such as Classes and Objects. In other words, you try to give characteristics to the entities you want to work with, as if they were physical objects sitting next to you. For instance, if you’re thinking about representing the concept of a door on a video game, you might create a class that represents the type of object, such as GameObject which has characteristics such as the coordinates where that object is located, the game scene where it’s supposed to appear, maybe even a numerical value representing that hit points that the door has. The point is that you’ve classified it as a GameObject and that through that classification you’ve given it some properties that other objects in this game world might share (other GameObject ‘s if you will).

That generic classification that allows you to group entities together is called a “Class” and every entity that fits into that class is called an “Object” .

Mind you, while these concepts work wonderfully to represent real-world objects in our code, they can — and normally are — used to represent any type of entity. For example, a linked list of values can be represented with a few classes, and there is nothing tangible about that. But the fact that you’re mentally turning these abstract concepts into physical objects is what makes OOP so intuitive, at least in regards to the basic concepts of the paradigm.

What’s the benefit of OOP?

Why is OOP better than FP? That’s the wrong question to ask, it’s no better nor worse! It’s different.

Some people will identify a lot more with OOP than others who might prefer FP for the type of problems they need to solve. Or any other paradigm really.

That being said, there are some interesting benefits to this approach:

  1. Given how it deals with turning abstract concepts into physical objects, you’re able to represent those objects and their relationships easily with graphical diagrams, such as UML. This allows you to represent a high-level overview of your complex business logic on a graphical diagram. That in turn simplifies that task of planning and showcasing ideas and concepts to others before you actually write a single line of code.
  2. It’s intuitive to think about. While not every problem lends itself to be solved through OOP, the ones that do are easier to reason through this mental model. We’re wired to work with and to manipulate them, so using it to think about our problems becomes second nature.
  3. Logic is where logic is supposed to be. Think about it this way: other than the properties of a door, or even of a GameObject you’ll also want to interact with those objects. What makes more sense? To put that code in a separate file, filled with unrelated functions, or to put everything inside a single file, all within the confines of an entity called “class” and making sure that every instance of it (and nothing else) has access to that code?
  4. There are tons of pre-established patterns to help you solve your problems. This is in part because of the paradigm and in part because it’s so widely used that many developers have come up with design patterns to help template some typical solutions. For example, if you need to access an entity that can only be instantiated once, you might want to resort to the Singleton pattern, if you need reactivity in your logic, you might want to check out the Observer pattern, and so on. They’re not pre-built solutions, but rather templates you can incorporate into your code to help you find the best way to solve your problems.

The list can probably go on, it being one of the main programming paradigms right now, it has been analyzed, researched and extended to its limits.

Is there a downside to OOP?

Of course there is! There is no silver bullet when it comes to programming paradigms, and OOP is no exception.

Some problems do not lend themselves to be reasoned about in terms of physical (or even conceptual) objects. This means that going with an OOP approach you’re trying to force a square into a circle. If the circle is big enough or the square small enough, it’ll fit and you’ll make it work, but it’s never going to be a perfect match.

The other downside to OOP is that it’s very complex. My explanation here only scratches the surface of everything that OOP is, I haven’t even discussed inheritance, encapsulation, polymorphism or any of the other fancy words that resound around this paradigm.

Fully understanding and taking advantage of OOP requires years of reading and experience combined. Mind you, it’s no less powerful because of that, but it’s definitely not easy to pick up.

Finally, it being such a powerful paradigm, some languages take it to the extreme causing a lot of overhead simply so they can comply with every bit of the paradigm. Let me explain: some languages are 100% OOP, meaning that they don’t accept any other alternative and if you use them to solve simple problems, you’ll have to write more boilerplate code than actually needed to solve the problem at hand. This of course is justified if you’re working on a large application that requires a lot of internal structure. So it’s rather a matter of using them to solve the right type of problems.


I always say that programming languages are like tools, you need to use the right one for each activity, and programming paradigms are exactly the same. They are not meant to solve every problem, some of them work better for a wider range, while others have less flexibility. In either case you’ll make the most of them if you understand the type of problems they help solve and what languages can help work on one or the other.

As a developer, knowing about the different paradigms will help you think about the way you solve each problem. And if you’re lucky to be using a language that allows for more than one, you can adapt your coding style without changing languages. That’s a great way to practice a new paradigm with very little impact.

What other paradigm did I miss that you consider important for others to know about? Leave your thoughts in the comments!

Did you find this article valuable?

Support Fernando Doglio by becoming a sponsor. Any amount is appreciated!