Programming Paradigms

A paradigm is a way of thinking about how code should be structured. Most real languages mix several paradigms, but each was shaped by one dominant philosophy. Knowing the paradigms lets you quickly orient in any new language.

Why this matters for an IT person

Every language you’ll encounter in IT has a paradigm pedigree:

  • Bash is imperative + procedural
  • Python is multi-paradigm (procedural, OOP, functional)
  • Ansible YAML is declarative
  • Terraform HCL is declarative
  • Go is procedural with some OOP and functional influence
  • SQL is declarative

When you read unfamiliar code, recognising the paradigm tells you how to mentally model it.

The four main paradigms

1. Imperative — “do this, then this, then this”

The oldest and most intuitive style. Code is a list of steps that change state.

total = 0
for number in [1, 2, 3, 4, 5]:
    total = total + number
print(total)   # 15
  • Mental model: a machine with state; instructions mutate that state
  • Pros: matches how computers actually work; easy to learn
  • Cons: as programs grow, tracking “what state is where” becomes hard
  • Languages: C, Bash, assembly

2. Procedural — imperative + organised into reusable functions

An evolution of imperative. Same idea, but instead of one long script, you break it into procedures (functions) with arguments and return values.

def sum_of(numbers):
    total = 0
    for n in numbers:
        total += n
    return total
 
print(sum_of([1, 2, 3, 4, 5]))
  • Pros: reusable, testable, readable
  • Languages: C, Go, Python (when not doing OOP), classic shell scripts

3. Object-Oriented (OOP) — bundling state + behaviour together

Data and the functions that operate on that data are bundled into objects, created from classes.

class Counter:
    def __init__(self):
        self.value = 0
 
    def increment(self):
        self.value += 1
 
c = Counter()
c.increment()
c.increment()
print(c.value)   # 2

Core OOP concepts:

  • Class — a blueprint

  • Object / instance — a concrete thing made from the blueprint

  • Encapsulation — data is kept inside the object; outside code uses methods to access it

  • Inheritance — a class can extend another (“a Dog is an Animal”)

  • Polymorphism — different classes with the same method name can be used interchangeably

  • Pros: natural for modelling real-world entities (users, orders, network devices)

  • Cons: overused; inheritance hierarchies can become a tangled mess

  • Languages: Java, C#, Python, Ruby, TypeScript

4. Functional — programs are compositions of pure functions

State and mutation are avoided. Everything is a function that takes input and returns output, with no side effects.

numbers = [1, 2, 3, 4, 5]
total = sum(numbers)   # sum is a pure function
doubled = list(map(lambda x: x * 2, numbers))
evens = list(filter(lambda x: x % 2 == 0, numbers))

Core ideas:

  • Pure functions — same input → same output, no side effects (no global state, no I/O)

  • Immutability — data isn’t modified in place; new values are returned

  • Higher-order functions — functions that take or return other functions (map, filter, reduce)

  • Pros: easier to reason about; parallelism is safer; fewer bugs from shared mutable state

  • Cons: I/O and side effects still exist; the purist version needs clever tricks

  • Languages: Haskell, Erlang, Elixir, Clojure; functional style in Python/JS/Scala

Declarative vs Imperative — a different axis

Separate from the 4 paradigms above, there’s a meta-distinction:

  • Imperative code says how: “open a loop, iterate, update, return”
  • Declarative code says what: “I want the sum of these numbers” — the runtime figures out how

SQL is declarative. So is Ansible YAML, Terraform HCL, Kubernetes manifests, and HTML. You describe the end state; something else makes it happen.

See Declarative vs Imperative Automation for how this plays out in Ops.

Multi-paradigm is the norm

Python famously supports all four. Here’s the same task in different styles:

# Procedural
def count_active_users(users):
    count = 0
    for u in users:
        if u['active']:
            count += 1
    return count
 
# Object-oriented
class UserList:
    def __init__(self, users): self.users = users
    def count_active(self): return sum(1 for u in self.users if u['active'])
 
# Functional
count_active = lambda users: sum(1 for u in users if u['active'])

None is “right.” They’re different lenses for the same problem.

What to learn first

For IT / Ops work:

  1. Procedural thinking — break tasks into functions with clear inputs/outputs
  2. Declarative tools (YAML configs, SQL, Ansible) — read and reason about them
  3. A bit of OOP — enough to read libraries and frameworks
  4. Functional style (list comprehensions, map/filter in Python) — for data wrangling

Full functional / pure OOP / dependency-injected enterprise architecture is a different world. Don’t need it for ops.

See also