CMPUT-274 · Lesson 1 of 12 · 30 min

What a programming paradigm is

A programming paradigm is a way of thinking about what a program is. In one paradigm a program is a sequence of commands that change the contents of memory. In another it is a set of mathematical functions that turn inputs into outputs. In a third it is a community of objects that send each other messages, and in a fourth it is a collection of facts and rules from which a system deduces answers. Each view comes with its own building blocks, its own idea of what good code looks like, and its own typical bugs. This course studies the major paradigms side by side so that you can recognize them, use them deliberately, and choose among them. You already know imperative programming from CMPUT-174 and CMPUT-201; here you will see why it is only one option, and often not the clearest one.

Why paradigms matter

Every general-purpose language can compute anything any other can compute; that is the Church–Turing thesis you meet again in CMPUT-474. So the choice of paradigm is not about what is possible. It is about what is easy to say, easy to get right and easy to change. A paradigm shapes the questions you ask when you design: what are the steps, what are the transformations, who is responsible for this data, or what must be true of the answer. It also shapes the mistakes you are prone to. Imperative code invites bugs in the order of updates; object-oriented code invites tangled hierarchies; functional code can hide performance costs; concurrent code invites races.

Modern languages are mostly multi-paradigm. Python supports procedural, object-oriented and functional styles, and has libraries for asynchronous and logic programming. Java began as an object-oriented language and added lambdas and streams in 2014, records in 2021 and pattern matching in later releases. Rust mixes functional ideas such as algebraic data types and iterators with low-level control. JavaScript combines prototypes, first-class functions and an event loop. Knowing the paradigms lets you read these languages as combinations of familiar ideas rather than as unrelated collections of features.

A short history

The earliest programs were imperative because machines are imperative: a processor fetches an instruction, changes a register or a memory cell, and moves to the next instruction, exactly as you saw in CMPUT-229. Fortran (1957) let scientists write formulas instead of machine instructions but kept the step-by-step model. Lisp (1958) came from a different tradition, Alonzo Church's lambda calculus, and treated functions as values that could be built and passed around. ALGOL and later C popularized structured programming: blocks, loops and procedures instead of arbitrary jumps, a shift argued for in Edsger Dijkstra's 1968 letter against the goto statement.

Simula (1967) introduced classes and objects to model simulations, and Smalltalk in the 1970s built a whole environment out of objects exchanging messages. C++ (1985) and Java (1995) brought objects to industry. Prolog (1972) showed that a program could be a set of logical facts and rules. ML and Haskell developed typed functional programming with algebraic data types, pattern matching and type inference. Erlang (1986) built concurrency around isolated processes that communicate only by messages. Since the 2000s, the mainstream has absorbed ideas from all of these: lambdas in Java and C++, pattern matching in Python 3.10, async functions in almost every language.

The major paradigms covered in this course
ParadigmA program is…Main building blockEarly example language
Imperative / procedurala sequence of commands that change statestatement, procedureFortran, C
Object-orientedobjects that hold state and respond to messagesobject, classSimula, Smalltalk
Functionalexpressions built from functionsfunction, valueLisp, ML, Haskell
Logicfacts and rules a system reasons overrelation, ruleProlog
Concurrent / actorindependent activities that communicateprocess, messageErlang
Event-driven / reactivehandlers that respond to events over timecallback, streamVisual Basic, JavaScript
Diagram with two branches. The imperative branch leads from machine code to procedural and then object-oriented programming. The declarative branch splits into functional and logic programming. Concurrency and event-driven programming appear as cross-cutting styles that combine with either branch.
Imperative and declarative branches, with concurrency and events cutting across both

Imperative and procedural programming

In the imperative paradigm, the program's state is the set of values held in variables at a given moment, and statements change that state. Assignment is the central operation. The order of statements matters, because each one sees the effects of those before it. Procedural programming organizes imperative code into named procedures, so a large task becomes a set of smaller ones that call each other. C, Pascal and the early parts of most CMPUT-174 programs are procedural.

An imperative total: a loop updates two state variables
# Imperative: describe HOW, step by step, with a changing accumulator
orders = [("tea", 3.50, 2), ("scone", 2.75, 3), ("cake", 4.25, 0), ("coffee", 2.95, 4)]

total = 0.0
count = 0
for name, price, qty in orders:
    if qty > 0:
        total = total + price * qty
        count = count + 1
print(f"{count} items ordered, total {total:.2f}")
Output
3 items ordered, total 27.05

The loop works by repeatedly changing `total` and `count`. To understand it you have to simulate the machine in your head: what are the values after the first iteration, the second, and so on. That is fine for six lines, but in a long function with many variables updated in many places, the reader must track a growing amount of state. Bugs arise when an update is in the wrong place, happens twice, or is skipped on one path.

Declarative programming

Declarative programming describes what result you want and leaves more of the how to the language or library. SQL is the most widely used declarative language: a query states which rows and columns you want, and the database decides the order of operations, which indexes to use and whether to run in parallel. CMPUT-291 covers SQL in detail. Functional and logic programming are the two main declarative paradigms in general-purpose code.

The same total as a filtered transformation followed by a sum
# Declarative (functional style): describe WHAT the result is
orders = [("tea", 3.50, 2), ("scone", 2.75, 3), ("cake", 4.25, 0), ("coffee", 2.95, 4)]

ordered = [(name, price * qty) for name, price, qty in orders if qty > 0]
total = sum(amount for _, amount in ordered)
print(f"{len(ordered)} items ordered, total {total:.2f}")
Output
3 items ordered, total 27.05

Nothing in the second version is reassigned. Each name is defined once, as a description of a value: `ordered` is the list of line amounts for items with a positive quantity, and `total` is their sum. A reader can check each line on its own. The order of the two lines matters only because the second refers to the first, the same way one definition in mathematics can depend on another.

Worked problem

Recognizing paradigms in code

Classify each fragment as mainly imperative, functional, object-oriented or logic style: (a) `cart.add(item); cart.apply_coupon(code)`; (b) `sorted(words, key=len)[-1]`; (c) `i = 0; while a[i] != x: i += 1`; (d) a rule stating that X is an ancestor of Y if X is a parent of Y, or X is a parent of some Z who is an ancestor of Y.

Show solution
  1. (a) sends messages to an object, which updates its own internal state: object-oriented.
  2. (b) composes functions on values and creates no variables that change: functional.
  3. (c) updates an index variable in a loop until a condition holds: imperative.
  4. (d) states a relation in terms of facts and a recursive rule, with no steps: logic programming.

Answer: (a) object-oriented, (b) functional, (c) imperative, (d) logic. Real programs mix these; the question is which idea organizes each part.

Object-oriented, functional and logic programming in one example

To make the differences concrete, here is one small problem solved in three styles. The task is to model bank accounts that can receive deposits and report balances. In the object-oriented style, each account object keeps its balance private and exposes operations. In the functional style, an account is an immutable value and a deposit returns a new account. Lessons 2 to 6 develop both styles in depth; for now, notice how each one answers the question of where state lives.

One account, two paradigms: mutable object versus immutable value
from dataclasses import dataclass, replace

class Account:                       # object-oriented: state inside the object
    def __init__(self, owner):
        self._owner, self._balance = owner, 0
    def deposit(self, amount):
        self._balance += amount      # the object changes itself
    def balance(self):
        return self._balance

@dataclass(frozen=True)
class AccountValue:                  # functional: an immutable value
    owner: str
    balance: int = 0

def deposit(acct, amount):           # returns a NEW value; the old one is unchanged
    return replace(acct, balance=acct.balance + amount)

a = Account("Ana"); a.deposit(50); a.deposit(25)
print("OO:", a.balance())

v0 = AccountValue("Ana"); v1 = deposit(v0, 50); v2 = deposit(v1, 25)
print("FP:", v0.balance, v1.balance, v2.balance)
Output
OO: 75
FP: 0 50 75

The object-oriented version is natural when an account has an identity that persists while its state changes: it is the same account after the deposit. The functional version keeps every past version, which makes undo, auditing and concurrency easier, because no one can change a value another part of the program is reading. Neither is wrong. Lesson 12 returns to the question of when each fits.

Logic programming is the least familiar of the four. Instead of steps or functions, you write relations. A query such as which accounts have a balance over 100 is answered by searching for values that make the stated facts true. Lesson 11 builds a tiny logic engine in Python so you can see the search that Prolog performs for you.

Programming models below and above the language

A paradigm is a model of computation offered to the programmer, and it may differ from the model the machine implements. A Haskell program is compiled to imperative machine code, and a Python object is ultimately a C structure in the interpreter. The paradigm is a layer of abstraction, like the ones in CMPUT-229 lesson 1. Good abstractions let you reason at the higher level without constantly thinking about the lower one, but they leak: functional code can allocate more memory than expected, and object-oriented code can hide expensive calls behind innocent-looking method names.

Paradigms also appear above the language, in libraries and frameworks. A web framework may call your functions when requests arrive, which is event-driven programming even if every function is procedural inside. Spreadsheets are a dataflow paradigm: you define cells in terms of other cells and the spreadsheet recomputes dependants when an input changes. Build tools such as make, from CMPUT-201, are declarative: you state targets and dependencies and the tool decides what to rebuild.

Worked problem

Spotting a dataflow paradigm

In a spreadsheet, cell C1 contains =A1*B1 and D1 contains =C1+10. A1 changes from 4 to 5 while B1 is 3. Explain what happens in paradigm terms.

Show solution
  1. Each formula is a declaration of what a cell is, not a command executed once.
  2. Changing A1 is an event; the spreadsheet finds the cells that depend on A1, here C1, and then cells that depend on C1, here D1.
  3. It recomputes them in dependency order: C1 becomes 15, then D1 becomes 25.
  4. The user never wrote the order of updates; it follows from the dependency graph, the same idea as topological sorting in CMPUT-204 lesson 8.

Answer: C1 becomes 15 and D1 becomes 25. The spreadsheet is declarative and dataflow-driven: the program is the set of definitions, and the system schedules recomputation.

How this course is organized

Lessons 2 and 3 cover functional programming: pure functions, higher-order functions, recursion, immutability, laziness and pattern matching. Lessons 4 and 5 cover object-oriented programming: encapsulation, polymorphism, inheritance, composition and design principles. Lesson 6 looks at design patterns and shows how the same problem is solved differently in each paradigm. Lesson 7 studies type systems, and lesson 8 compares ways of handling errors. Lessons 9 and 10 cover concurrency with threads, processes, locks and messages, and lesson 11 covers asynchronous, event-driven, logic and reactive programming. Lesson 12 is a capstone in which you design a multi-paradigm program.

Most examples are in Python, because it supports every paradigm we study and you already know it. Where another language shows an idea more clearly, we use a short Java or Rust program. All programs in the course were compiled and run when the course was built, and the output you see is their real output. You will not need to install Java or Rust to follow the lessons, but trying the examples yourself is the best way to learn.

Design questions and the paradigms built around them
Question you ask while designingParadigm that answers it most directly
What steps transform the input, in what order?Imperative / procedural
What is the output as a function of the input?Functional
Who owns this data and what operations does it support?Object-oriented
What must be true of an answer?Logic / declarative
What can happen at the same time, and how do parts communicate?Concurrent / actor
What should happen when this event occurs?Event-driven / reactive

Common mistakes

  • MistakeThinking a language belongs to exactly one paradigm.

    InsteadMost current languages are multi-paradigm; a paradigm is a style of organizing code that a language may support well or poorly.

  • MistakeBelieving a paradigm determines what can be computed.

    InsteadAll general-purpose paradigms are equally powerful in principle; they differ in clarity, safety and convenience.

  • MistakeTreating declarative code as code that has no order at all.

    InsteadDeclarative code leaves the evaluation order to the system, but definitions still depend on each other; dependencies impose an order.

  • MistakeAssuming functional means using map and lambda everywhere.

    InsteadThe core of functional programming is avoiding hidden state changes; comprehensions and plain function calls can be just as functional.

  • MistakeSeeing object-oriented programming as only about classes and inheritance.

    InsteadThe central idea is objects that own their state and expose behavior; inheritance is one mechanism, often not the best one.

Summary and key terms

  • A programming paradigm is a model of what a program is: commands, functions, objects, rules, communicating processes or event handlers.
  • Paradigms do not change what can be computed; they change how easy code is to write, read, verify and modify.
  • Imperative code changes state step by step; declarative code describes results and leaves more of the evaluation to the system.
  • Functional and logic programming are declarative; object-oriented programming usually organizes imperative state inside objects.
  • History moved from machine-level imperative code to structured, object-oriented, functional, logic and concurrent styles, and modern languages combine them.
  • A paradigm is an abstraction over the machine and may appear in a language, a library or a tool such as a spreadsheet or make.
  • The rest of the course studies each paradigm and ends with choosing and combining them in a design.
programming paradigm
A fundamental style or model of programming that determines how programs are structured and how computation is expressed.
imperative programming
Programming by issuing commands that change the program state, usually through assignment.
procedural programming
Imperative programming organized into named procedures or functions that call each other.
declarative programming
Programming by describing the desired result rather than the steps to compute it.
functional programming
A declarative paradigm in which programs are built by applying and composing functions, avoiding mutable state.
object-oriented programming
A paradigm in which programs are organized around objects that combine state and behavior and interact by method calls.
logic programming
A declarative paradigm in which programs are facts and rules, and computation is a search for values that satisfy queries.
program state
The values of all variables and data structures at a given moment of execution.
multi-paradigm language
A language that supports more than one paradigm, such as Python, Java, Rust or JavaScript.
dataflow
A model in which computation is driven by the availability of data, with values recomputed when their inputs change.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. In one sentence each, state what a program is in the imperative, functional and object-oriented paradigms.

    Show answer

    Imperative: a sequence of commands that change state. Functional: an expression built from functions that maps inputs to outputs. Object-oriented: a set of objects that hold state and interact by calling each other's methods.

  2. Why can no paradigm compute something another general-purpose paradigm cannot?

    Show answer

    Every general-purpose paradigm is Turing-complete, so each can simulate the others; differences are in expressiveness and convenience, not in computability.

  3. Rewrite `squares = []` followed by `for x in range(10): squares.append(x * x)` in a declarative style.

    Show answer

    `squares = [x * x for x in range(10)]` or `list(map(lambda x: x * x, range(10)))`.

  4. Give one advantage of the immutable account value over the mutable account object.

    Show answer

    Old versions remain available, so undo and auditing are easy, and no other part of the program can see the value change unexpectedly, which also helps with concurrency.

  5. Give one advantage of the mutable account object.

    Show answer

    It models an account with a stable identity directly and avoids creating new values on every update, which can be simpler and cheaper.

  6. Name a declarative language or tool you have used outside general-purpose programming.

    Show answer

    Examples: SQL, HTML, CSS, regular expressions, make, spreadsheet formulas.

  7. Which language introduced classes and objects, and for what purpose?

    Show answer

    Simula, in the 1960s, to model simulations in which many entities interact.

  8. What is the main idea behind structured programming?

    Show answer

    Using sequence, selection (if) and iteration (loops) inside blocks and procedures instead of arbitrary jumps such as goto.

  9. Why do functional programs still compile to imperative machine code?

    Show answer

    The processor executes instructions that change registers and memory; the functional paradigm is an abstraction implemented on top of it by the compiler and runtime.

  10. Classify a web framework that calls your function whenever a request arrives.

    Show answer

    Event-driven: the framework owns the main loop and invokes handlers in response to events.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz