Programming Paradigms Presented Plainly

Cory L. RahmanCory L. Rahman
11 min read

Drawing lessons from these paradigms will enhance your programming skills and help you write more stable, readable, and reusable code.

1) Intro: What is a "programming paradigm"?

A programming paradigm is a strategic approach to writing code. Each paradigm establishes a set of guiding principles aimed at producing code that is more stable, readable, and/or reusable.

The collective insights and experiences of past programmers have given rise to today's paradigms. As a result, programming paradigms typically introduce beneficial constraints and are frequently used to categorize programming languages based on their features.

It's crucial to recognize that the most effective software often merge these paradigms in modern multi-paradigm languages like JavaScript, Python, Java, C++, etc. The focus of this article is to enhance your programming skills by drawing lessons from all these paradigms.

2) Imperative and Declarative Programming

Imperative and declarative programming are long-established and polar-opposite paradigms frequently used to characterize other programming approaches. They differ in that imperative programming provides the computer with step-by-step instructions on how to accomplish a task, whereas declarative programming specifies a desired result and allows the computer to determine the means to achieve it.

2.1) Imperative Programming

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

var name = "Lea"
var greeting = "Hi!";
var message = name + " says " + greeting;
console.log(message); // Result: "Lea says hi!"
  • This JavaScript code shows a series of statements that change the state of the program.

  • Control flow is often achieved using conditional branching (like if else) 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 C and Assembly are considered imperative programming languages.

2.2) Declarative Programming

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:

SELECT name, greeting
FROM people_table
WHERE name='Lea'
-- Result: Lea | Hi!
  • This SQL code describes what we want, not the steps for 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 programming, reusable groups of code called procedures or functions are used to change the state of the program. It is considered a type of imperative programming.

Here is an example of procedural programming:

var message = "";
function updateMessage(person, greeting) {
    message = person + " says " + greeting;
}

updateMessage("Lea", "hi!"); // Activate function
console.log(message); // Result: "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/function updateMessage is called (activated) using parentheses like this updateMessage("Lea", "hi!");.

The updateMessage function accepts two text arguments, "Lea" and "Hi!", and assigns them to its parameter variables (person and greeting) based on the order. That means when the function is activated, person = "Lea" and greeting = "hi!".

Finally, the function changes the value of message to "Lea says Hi!". We could call this procedure/function again with different arguments to make changing the message more easily in the future, like so:

updateMessage("Lukas", "hey");
console.log(message); // Result: "Lukas says hey"

updateMessage("Apollo", "hey you!");
console.log(message); // Result: "Apollo says hey you!"

Notice how the value of message is changing each time we call the updateMessage function.

In some languages, procedures and functions differ in that functions can return data via a return keyword. In JavaScript and Python, there is no distinction as functions are used for both:

function getMessage(person, greeting) {
    return person + " says " + greeting;
}
newMessage = getMessage("Emilie", "Bonjour!")
console.log(newMessage); // Result: "Emilie says Bonjour!"

Notice how this time we did not modify the message variable. Instead the function returns the value and allows us to assign the new value to a newMessage variable.

Common Procedural Programming Terminology:

  • Procedure/function/subroutine: a group of code that can be activated/called remotely and repeatedly

  • Declaration: the definition of a new procedure/function including its name, inputs, and behavior

  • Call: to activate a procedure/function

  • Parameters: variables listed in a procedure/function declaration that define what input data can be given to a procedure/function

  • Arguments: the actual values of the input data provided to a procedure and assigned to its parameters

Key Takeaways 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 and assemble. OOP is all about organizing concepts in an understandable way and making messaging between different groups of code easier.

The Class and Object data structures are the cornerstones of object-oriented programming. In OOP, 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:

class Person {
   constructor(name, greeting) { // Constructor (function)
       this.name = name; // Property (variable)
       this.greeting = greeting; // Property (variable)
   }
   greet() { // Method (function)
       console.log(this.name + " says " + this.greeting);
   }
}
  • Person constructor: constructor(…), a function used to create an object from the class.

  • Person properties: name and greeting, variables that define attributes of the class and objects created from it.

  • Person method: greet(), a function that defines behavior of the class and objects created from it.

Next, we use the new keyword to create a “Lea” object from our Person class:

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

This line of code creates a Lea object using the Person class as a blueprint. The variable leaObject now holds this object created from the Person class. 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.

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

Here we call 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/activating the greet method, a function that is part of leaObject.

This greet method (a function) acts as an interface into the leaObject, abstracting the code so that it is still useful while not having to think about the inner workings of the leaObject.

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

var lukasObject = new Person("Lukas", "Hallo!");
var emilieObject = new Person("Emilie", "Bonjour!");

lukasObject.greet();  // Result: "Lukas says Hallo!"
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, and some additional ones as well:

class Player extends Person {
    constructor(name, greeting, score) {
        super(name, greeting);
        this.score = score;
    }
    sayScore() {
        console.log(this.name + "'s score is " + this.score)
    }
}

const tobyObject = new Player("Toby", "Hello", 90);

tobyObject.greet(); // Result: "Toby says Hello"
tobyObject.sayScore(); // Result: "Toby's score is 90"

Because the Player subclass extends the Person class, the new tobyObject has the properties and methods of both a Person and a Player. The super statement provides these properties and methods from the Person superclass. Inheritance makes writing new Classes easier and helps organize the relationship between them intuitively.

Common OOP Terminology

  • Class: a blueprint for creating Objects

  • Object: a group of code created using a Class that bundle together Properties (data) and Methods (behavior)

  • Property: a variable that is part of a Class or Object, typically it 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: data and the methods (Functions) to operate on them are bundled together in Objects and provide a public interface to control how the data and methods are used

  • Abstraction: hide complex functionality behind an easier-to-use interface such as public methods (functions) in a class

  • Inheritance: a new Class can be created based on another Class; the newly derived class inherits all the base Class’s properties and methods

  • Polymorphism: this is a larger topic in programming that allows programmers to use the same interface in different contexts to make things easier for us. For example, when we created the new Player subclass from the Person class, we could have also re-implemented the greet() method to say "Player Toby says Hello", making the greet() method polymorphic because it changes based on which object it's called from.

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 (FP)

Functional programming takes a declarative approach, focusing on pure, first-class functions which accept arguments, return results, and use immutable data. This section will focus primarily on key aspects of Functional Programming that you can incorporate into other codebases, rather than strict FP.

Here is an example demonstrating some functional programming concepts:

function makeIntro(name) {
    return name + " says ";
}
function makeGreeting(makeIntroFunction, name, message) {
    return makeIntroFunction(name) + message;
}

makeGreeting(makeIntro, "Lea", "Hi!");
// Result: "Lea says Hi!"

Here we are defining two pure, first-class functions called makeIntro and makeGreeting:

  • Both these functions are pure because they only use data from their parameters, they return an output, and they have no side effects. This increases their consistency and stability by making sure outside changes can't unexpectedly change the results of the functions.

  • First-class functions can be passed as arguments to other functions. The makeGreeting function is called a higher-order function because it accepts a first-class function as an argument.

The function call is where all the action happens. The makeGreeting function is called/activated and given the makeIntro function as an argument, which is then called within the makeGreeting function and combined with the name ("Lea") and message ("Hi!") to give a final result of "Lea says Hi!".

Common Functional Programming Terminology

  • Function: a procedure that performs a task and returns an output

  • 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)

  • Immutable data: data that can't be changed/mutated; in programming, avoiding or minimizing mutable data, like data stored in variables, can help make more consistent functionality and avoid errors based on unexpected changes.

  • First-class functions: functions that are treated like any other variable; they can be passed and returned through other functions

  • Higher-order functions: functions that take first-class functions as arguments

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 and by improving refactorability because of both of these benefits)

  • Testable (by providing clear endpoints, inputs, and outputs for automated testing)

  • 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) Wrap-Up & Resources

Closing remarks:

  • Multi-paradigm programming languages like JavaScript and Python support all of these popular programming paradigms, and often the best 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 some of the most used and talked about, but there are other related paradigms including Structured Programming and Logic Programming too.

  • Try to apply concepts from these paradigms to write more stable, understandable, and usable code.

  • Happy coding!

Resources:

10
Subscribe to my newsletter

Read articles from Cory L. Rahman directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Cory L. Rahman
Cory L. Rahman

Ask me about code, I like to help! 🦆 Lead Software Engineer & Cartographer at Booz Allen. Mentor at Resilient Coders. Grad studies at George Mason University. Views are my own.