Popular Programming Paradigms Explained

Imperative vs. Declarative vs. Procedural vs. Object-Oriented (OOP) vs. Functional

Cory Leigh Rahman
9 min readSep 23, 2021

Knowing these paradigm styles will help you read and write code more effectively. Here’s an introduction with examples and terminology.

If there’s a particular paradigm you’re interested in, like Object-Oriented Programming (OOP), feel free to skip to that section for an introduction.

Contents

  1. Intro: What is a “programming paradigm” again?
  2. Imperative and Declarative Programming
  3. Procedural Programming
  4. Object-Oriented Programming
  5. Functional Programming
  6. Closing Remarks

1. Intro: What is a “programming paradigm”?

A programming paradigm is a strategy for writing code that aims to produce more stable, understandable, or reusable code by following the paradigm’s set of guiding principles.

Programming paradigms are often used to:

  1. Describe ways to organize code
  2. Help classify programming languages based on their features

Different paradigms have developed over time based on collective ideas and lessons-learned from past programmers. This is why paradigms typically constrain code in a helpful way.

It’s important to note that these paradigms can be used strictly on their own but are often combined together in modern, high-level, multi-paradigm languages like JavaScript and Python. Even if you don’t strictly adhere to a particular paradigm, the strategies you learn from from the different paradigms will make you a better programmer overall.

2. Imperative and Declarative Programming

Imperative and declarative programming are old, broad, polar opposite paradigms often used today to describe other programming paradigms. They differ in that imperative programming tells the computer step-by-step how to do something, while declarative programming declares an end-result and lets the computer figure out how to achieve it.

Five popular programming paradigms; imperative and declarative are more broad categories

Imperative programming uses statements which describe how to change a program’s state step-by-step. Here is an example of imperative programming:

 1| var name = "Lea"
2| var greeting = "Hi!";
3| var message = name + " says " + greeting;
4| // Result: The message is "Lea says Hi!"
  • This JavaScript code shows a series of statements which change the state of the program
  • Control flow is often achieved using conditional branching (like if , else, or switch) and loops (like for or while)
  • Imperative programming is the oldest and closest to machine language which is why a lot of low-level programming languages like assembly are considered imperative programming languages

Declarative programming describes what the intent of a program is, as opposed to describing how to do it. Here is an example of declarative programming:

 1| SELECT name, greeting
2| FROM people_table
3| WHERE name='Lea'
4| -- Result: Lea | Hi!
  • This SQL code describes what we want, not how to get it
  • Declarative programming is sometimes used as an umbrella term to describe code that isn’t imperative
  • Other examples of declarative languages include HTML, CSS, and regex

3. Procedural Programming

In procedural programing, reusable groups of code called procedures or functions are used to change the state of the program. It is considered a type of imperative programing.

Here is an example of procedural programming:

 1| var message = "";
2| function updateMessage(person, greeting) {
3| message = person + " says " + greeting;
4| }
5|
6| updateMessage("Lea", "Hi!");
7| // Result: The message is "Lea says Hi!"

First we define a variable named message and a procedure to change it named updateMessage (defined using the function keyword since this is JavaScript). The procedure updateMessage is called (activated) in line 6; it accepts two text arguments (“Lea” and “Hi!”), assigns them to its parameter variables (person and greeting) based on order, then changes the value of message to “Lea says Hi!”. We could call this procedure again with different arguments to change the message a second time, like so:

 8| updateMessage("Lukas", "Hey");
9| // Result: Now the message changed to "Lukas says Hey"

Procedures and functions also have the option to return a calculated value, for example:

 9| function getMessage(person, greeting) {
10| return person + " says " greeting;
11| }
12| newMessage = getMessage("Emilie", "Bonjour!")
13| // Result: newMessage is "Emilie says Bonjour!"

Common procedural programing terminology:

  • Procedure / subroutine: a group of code which can be remotely activated
  • Call: means to activate a procedure
  • Parameters: variables that define what input data can be given to a procedure
  • Arguments: the input data provided to a procedure and assigned to its parameters
  • return: a common keyword for defining the output from a procedure or function

Key take-aways for procedural programming

Use procedural programming to make your code more:

  • Understandable (by using well-named procedures to add logical separations)
  • Reusable (by using the same procedures multiple times and using procedure arguments to use them in different ways)

4. Object-Oriented Programming (OOP)

By making code resemble real-world objects, object-oriented programming makes code easier to think about. It’s all about organizing concepts in an understandable way.

Three icons shaped like people; the first one is hollow and represents a Class blueprint for a “Person” while the latter two are labeled “Lea” and “Lukas”, the latter two are are objects created from the “Person” class.
In OOP a Class is a blueprint, and Objects are instances of a Class

The Class and Object data structures are the cornerstones of object-oriented programming. A Class is a blueprint which you can create Objects from. For example, if you had a Class blueprint called “Person” you might be able to create different people like Lea and Lukas from the “Person” blueprint.

Here is an example of object-oriented programming (OOP) using JavaScript. In this example our goal is to make a person named Lea say “Hi!”. We start by defining a class (a blueprint) called Person. The Person class will contain a constructor, two properties and a method:

  • Person constructor (used to create an object from the class): constructor
  • Person properties (attributes stored as variables): name and greeting
  • Person method (behavior stored as functions): greet()
 1| class Person {
2| constructor(name, greeting) {
3| this.name = name;
4| this.greeting = greeting;
5| }
6| greet() {
7| console.log(this.name + " says " + this.greeting);
8| }
9| }

Next we can create a “Lea” object from our Person Class:

10| var leaObject = new Person("Lea", "Hi!");

Line 10 creates a Lea object using the Person class as a blueprint. Because of how the constructor method of the class is written, the first argument (“Lea”) gets assigned to the name property, and the second argument (“Hi!”) gets assigned to the greeting property.

11| leaObject.greet();
12| // Result: "Lea says Hi!"

Line 11 calls leaObject’s greet() function to make Lea say “Hi!”. Notice the “dot” notation: a period (.) is a common syntax used to access a property or method inside the preceding object. In this case we are calling the greet method, a function that is part of leaObject.

We can also use the Person class to easily create more people, all of which have their own greet() method already built-in:

13| var lukasObject = new Person("Lukas", "Hallo!");
14| var emilieObject = new Person("Emilie", "Bonjour!");
15|
16| lukasObject.greet(); // Result: "Lukas says Hallo!"
17| emilieObject.greet(); // Result: "Emilie says Bonjour!"

Another common, but more advanced feature of OOP is inheritance. You can also use inheritance to create a Class based on another Class. Below we make a more specific kind of Person called a Player who has all the same properties and methods as Person, but some additional ones as well:

18| class Player extends Person {
19| constructor(name, greeting, score) {
20| super(name, greeting);
21| this.score = score;
22| }
23| sayScore() {
24| console.log(this.name + "'s score is " + this.score)
25| }
26| }
27|
28| const tobyObject = new Player("Toby", "Hello", 90);
29|
30| tobyObject.greet(); // Result: "Toby says Hello"
31| tobyObject.sayScore(); // Result: "Toby's score is 90"

Because the Player class extends the Person class, the new tobyObject has the properties and methods of both a Person and a Player. The super statement in line 20 provides these from the Person class. Inheritance makes writing new Classes easy and helps organize the relationship between them in an intuitive way.

Common OOP terminology:

  • Class: a blueprint for creating logical groups of code
  • Object: an entity created from a class blueprint
  • Property: a variable that is part of a Class or Object that describes an aspect or state of the Object
  • Method: a function that is part of a Class or Object and allows it to act
  • constructor: a common term and keyword for a special method only used to create Objects from a Class
  • this or self: common keywords to refer to other parts of the same Object from within
  • Encapsulation: properties and methods can have designations like public or private in order to more carefully control how the Class is used
  • Abstraction: it’s best practice to make Classes easy to use by only exposing simple public Class methods that hide more complex functionality
  • Inheritance: a new Class can be created based on another Class; the new derived class inherits all the base Class’s properties and methods
  • Polymorphism: a method may change functionality depending on the situation; for example, if a Person object has a method getName() they might say “Lea”, but if a Player object inherits and modifies functionality from the Person class, their getName() method might return an online name like “Lea_042”.

Key take-aways for object-oriented programming (OOP)

Use object-oriented programming to make your code more:

  • Understandable (by grouping behavior into Classes and Objects and providing clear relationships between different parts of the program)
  • Reusable (by using Classes to generate Objects, and by using Inheritance & Polymorphism to reuse Classes and their methods)
  • Stable (by encapsulating and protecting code inside Objects, and by providing clear endpoints for automated testing, improving refactorability)

5. Functional Programming

Functional programming takes a more declarative approach to procedures, focusing code around pure, first-class functions which accept arguments and return results.

Here is an example demonstrating some functional programming concepts:

 1| function makeIntro(name) {
2| return name + " says ";
3| }
4| function makeGreeting(makeIntroFunction, name, message) {
5| return makeIntroFunction(name) + message;
6| }
7|
8| console.log(makeGreeting(makeIntro, "Lea", "Hi!"));
9| // Result: "Lea says Hi!"

First we are defining two pure, first-class functions called makeIntro and makeGreeting. These functions are pure because they only use data from their parameters, they return an output, and they have no side effects. Because this is JavaScript, they are also inherently first-class functions and can be passed as arguments to other functions. The makeGreeting function is also a higher order function because it accepts a first-class function as an argument.

Line 8 is where all the action happens. Because all functions are pure there’s less chance of an outside change unexpectedly changing the result of this code. That’s one of the benefits of functional programming. The code is also quite modular. For example, currently the result is “Lea says Hi!”, but if you wanted to change the intro style you could simply provide a different makeIntro function to makeGreeting and instead get something else like “[Lea]: Hi!”.

Common functional programming terminology:

  • Function: a procedure which performs a task and returns an output
  • First-class functions: when functions are treated like any other variable; they can be modified, assigned to variables, and even passed and returned through other functions (known as higher-order functions)
  • Pure function: a function which 1) only uses data provided via its parameters, ensuring consistency (no hidden inputs like global variables), and 2) has no side effects (no hidden outputs)
  • Side effects: when a function changes or mutates anything outside the scope of the function, circumventing the return statement (e.g. hidden outputs like mutable data and calls to procedural functions)

Key take-aways for functional programming

Use functional programming to make your code more:

  • Stable (by using pure functions to reduce the chance of runtime errors caused by hidden inputs and hidden outputs, by providing clear endpoints for automated testing, and by improving refactorability because of both of these benefits)
  • Reusable (by forcing functions to be more modular and by allowing first-class functions to be passed as arguments for deep customization)
  • Understandable (by making logic easier to follow through strict pure function parameters and by showing intent up-front)

6. Closing Remarks

  • Multi-paradigm programming languages like JavaScript and Python support the use of all five of these popular programming paradigms, and often good software solutions use a combination of these styles. For example, sometimes using pure functions for OOP class methods can make code more testable and understandable.
  • The five programming paradigms discussed in this article are currently the most used and talked about, but there are others including Logic Programming and Structured Programming too.
  • Try applying concepts from these paradigms to write more stable, understandable, and reusable code.
  • Happy coding!

Further Reading:

--

--