Programming Paradigms Presented Plainly
Imperative, Declarative, Procedural, Object-Oriented (OOP), and Functional: What Are They?
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 (likefor
orwhile
).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
andgreeting
, 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 Classthis
orself
: common keywords to refer to other parts of the same Object from withinEncapsulation: 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 thegreet()
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: