Programming Paradigms

A paradigm is a fundamental style of thinking about and structuring a program. Most real languages support more than one paradigm; the trade-offs between them run through every design decision covered later in this module. A dedicated Programming Paradigms module (with worked examples per paradigm) is planned as a future lesson.

Imperative programming describes computation as a sequence of explicit steps that change program state ("do this, then this, then this"). Declarative programming describes what result you want, leaving the how up to the language or system ("give me all users older than 18").

Different problems fit different mental models more naturally. Step-by-step control flow suits low-level, performance-critical logic; describing desired outcomes suits data queries, configuration, and UI descriptions where the underlying steps are repetitive and better left to the system.

Imperative: C, most of Python and JavaScript's everyday code, assembly. Declarative: SQL (describe what data you want, not how to fetch it), HTML/CSS (describe what the page should look like), and much of functional programming.

Imperative is like giving someone turn-by-turn driving directions: 'go straight two blocks, turn left, go one more block.' Declarative is like telling a GPS your destination and letting it figure out the turns: 'take me to 123 Main Street.'

The imperative version spells out exactly how to build the list, step by step. The declarative version only states the desired result — which numbers satisfy the condition — and leaves the mechanics to the query engine.

Object-oriented programming (OOP) organizes code around objects that bundle data and behavior together (classes, inheritance, polymorphism) — covered in full depth in the future Object-Oriented Programming module.

Functional programming treats computation as the evaluation of pure functions, favoring immutability and avoiding shared mutable state — covered in full depth in the future Functional Programming module.

Procedural programming is imperative programming organized around procedures/functions that operate on data, without necessarily bundling data and behavior into objects (classic C is largely procedural).

Event-driven programming structures a program around responding to events (a button click, a network message arriving) rather than a single linear flow — dominant in GUI programming and JavaScript's browser model, and closely tied to the Async Programming module.

Logic programming (Prolog) expresses a program as a set of facts and rules, and lets the language's inference engine derive answers to queries — a very different model from anything imperative, covered in a future dedicated Logic Programming module.

Most real-world languages are multi-paradigm: Python supports procedural, object-oriented and functional styles; JavaScript mixes procedural, object-oriented, functional and event-driven idioms freely within the same file.

Treating paradigms as mutually exclusive categories a language must pick one of — nearly every modern general-purpose language mixes paradigms, sometimes within a single function.

Assuming object-oriented is inherently 'better' or 'more modern' than procedural or functional — each paradigm suits different problems, and forcing OOP onto a problem that's naturally a simple data transformation often adds unnecessary complexity.

Confusing 'declarative' with 'easy' — SQL and CSS are declarative but can still express extremely intricate, hard-to-reason-about logic; declarative just means the syntax describes outcomes rather than steps.

Writing a data-transformation pipeline in a functional style (map/filter/reduce) because it's easier to reason about and test than an equivalent hand-written loop with mutable accumulator variables.

Modeling a simulation's entities (cars, sensors, ECUs) as objects with OOP because their shared behavior and internal state naturally map to classes and inheritance.

Structuring a UI's interaction logic around events (click handlers, message callbacks) because the program's actual behavior is inherently reactive to user or system events, not a single top-to-bottom script.

Paradigms as constraints, not just styles A paradigm can be understood as a deliberate set of constraints a language imposes to make certain classes of bugs harder to write. Pure functional programming's ban on mutable shared state, for instance, makes entire categories of concurrency bugs (data races) structurally impossible in code that stays purely functional — the constraint is the feature.

Multi-paradigm language design Languages like Python, JavaScript, Scala, C++ and Rust deliberately support multiple paradigms so a developer can pick the best tool for each specific piece of logic within one codebase — e.g. functional-style data transformations alongside object-oriented domain modeling, within the same program.

Paradigm choice interacts with performance and concurrency Pure functional, immutable-data styles can make certain optimizations (safe parallelization, structural sharing of data) much easier for a runtime to apply automatically, at the potential cost of extra allocation compared to in-place mutation — a trade-off explored much further in the Functional Programming and Concurrency modules.

What's the difference between imperative and declarative programming?

Imperative programming specifies the exact sequence of steps to reach a result and explicitly manages state changes along the way. Declarative programming specifies the desired result and leaves the implementation's steps to the underlying system.

Is Python object-oriented or procedural?

Both — and functional too. Python is a genuinely multi-paradigm language; you can write purely procedural scripts, fully object-oriented class hierarchies, or functional-style code using map/filter/lambda, often mixed within the same program.

Give an example of a declarative language.

SQL — a SQL query describes what data should be returned (rows matching certain conditions) without specifying the exact algorithm (which index to use, in what order to scan tables) the database engine uses to produce it.

Why might a functional style make concurrent code easier to reason about?

Because functional programming favors immutable data and pure functions with no shared mutable state — if data can't be mutated after creation, there's no way for two threads to race on modifying it, eliminating a major category of concurrency bugs by construction.

Imperative (Python):
  evens = []
  for n in numbers:
      if n % 2 == 0:
          evens.append(n)

Declarative (SQL-style intent):
  SELECT n FROM numbers WHERE n % 2 = 0