Lesson 6 of 40 beginner 8 min read

Paradigms & language families

Key takeaways Paradigms — styles for organising code, not rival tribes. Multi-paradigm — real languages blend several at once. Pure vs mutable — controlling state is the deep dividing line.

When people argue about programming languages, they are usually arguing about paradigms — the underlying styles for structuring code and reasoning about what a program does. A paradigm is not a feature you switch on; it is a mental model. Understanding the handful of major paradigms gives you a map: you can look at any unfamiliar language and recognise the shapes inside it, instead of memorising a thousand unrelated rules. This lesson lays out that map, then shows why almost every language you will actually use mixes paradigms freely.

Imperative and procedural

The imperative paradigm is the oldest and most direct: you tell the computer how to do something as an ordered sequence of statements that change state. Assign a value, loop, increment a counter, branch on a condition. It mirrors how the hardware really works, which is why it feels natural and why early languages were imperative.

Procedural programming is imperative code organised into reusable procedures (functions). Instead of one long script, you factor work into named routines that call each other. C is the classic procedural language.

int sum_to(int n) {
    int total = 0;
    for (int i = 1; i <= n; i++) {
        total += i;      // mutate state step by step
    }
    return total;
}

The defining trait here is mutable state: variables change over time, and the order of statements matters. That power is also the risk — bugs often come from state changing when or where you did not expect.

Object-oriented

Object-oriented programming (OOP) bundles state and the behaviour that acts on it into objects. An object has fields (data) and methods (functions that operate on that data). The big ideas are:

  • Encapsulation — hide internal data behind a clean interface.
  • Inheritance — derive specialised types from general ones.
  • Polymorphism — treat different types uniformly through a shared interface.

OOP shines when a problem decomposes naturally into “things” with identity and lifecycle — a User, a Connection, a Receiver tuned to a frequency. Java, C#, Ruby and Python all support it strongly. Critics note that overusing inheritance creates rigid, tangled hierarchies, which is why modern OOP leans on composition (“has-a”) over deep inheritance (“is-a”).

Functional

Functional programming (FP) treats computation as the evaluation of functions, ideally pure ones. A pure function:

  • always returns the same output for the same input, and
  • has no side effects — it does not mutate shared state, write files, or print.

Pure functions are easy to test, reason about, and run in parallel, because they cannot interfere with each other. FP favours immutable data, higher-order functions (functions that take or return functions), and composing small transformations into pipelines. Haskell is purely functional; Elixir, Clojure and Scala lean functional; and most mainstream languages now offer map, filter and reduce.

# Pure pipeline: each step transforms, nothing is mutated
samples = [0.1, -0.4, 0.9, -0.2]
scaled  = map(lambda x: x * 2, samples)
loud    = filter(lambda x: abs(x) > 1.0, scaled)

The contrast with imperative code is the mutable state vs pure functions axis. Imperative code says “change this variable, then that one.” Functional code says “produce a new value from old values.” Most subtle bugs live in shared mutable state, so reducing it is a recurring theme across modern language design.

Declarative (including logic and SQL)

Declarative programming flips the question from how to what: you describe the result you want and let the system figure out the steps. FP is one declarative family, but the term covers more:

  • SQL — you declare what rows you want; the database engine plans how to fetch them. SELECT name FROM stations WHERE freq > 144000000 never tells the engine which index to scan.
  • Logic programming (Prolog) — you state facts and rules, then pose queries; the engine searches for solutions that satisfy them.
  • Markup and config (HTML, CSS, Terraform) — you declare a desired structure or end-state, not a procedure.

Declarative code is often shorter and harder to get subtly wrong, at the cost of giving up fine control over execution.

Quick check: what makes a function "pure" in functional programming?

Real languages are multi-paradigm

Here is the practical punchline: the paradigms above are not separate languages. Almost every language you will meet is multi-paradigm, and the “paradigm” is a style you choose for a given piece of code.

Language Procedural OOP Functional Declarative-ish
Python yes yes yes (map/comprehensions) partial
Rust yes traits/structs strong (iterators, closures) macros
Go yes structs + interfaces limited no
JavaScript yes prototypes/classes strong JSX/templating

Python lets you write a quick procedural script, define classes, or chain functional comprehensions. Rust combines struct-and-trait OOP-style code with genuinely functional iterators and a strong emphasis on immutability by default. Go is mostly procedural with lightweight interfaces and deliberately omits much functional machinery, favouring simplicity. Knowing the paradigms lets you read each language for what it offers rather than forcing it into one box.

Choosing a paradigm for the job

Because most languages let you mix styles, the practical skill is matching a paradigm to a problem rather than picking a “favourite”. A few rules of thumb:

  • Reach for imperative/procedural when you are close to the hardware or in a tight performance loop — the step-by-step model maps directly onto what the CPU does, and you want exact control over each operation.
  • Reach for object-oriented when the domain is full of long-lived “things” with identity and lifecycle — a connection, a session, a tuned receiver — and you want to hide their internals behind clean interfaces.
  • Reach for functional when you are transforming data and want each step to be independently testable, easy to reason about, and safe to run in parallel. Pure functions shine wherever shared mutable state would otherwise cause subtle bugs.
  • Reach for declarative when what you want is clearer than how to get it — querying data (SQL), describing infrastructure, or expressing rules.

The mark of an experienced developer is comfort moving between these within one codebase: a procedural inner loop, wrapped in an object that owns a resource, assembled from functional transformations, configured declaratively. None of these is wrong; each is a tool, and the paradigms are the labels on the toolbox.

Why this matters for signal pipelines

In radio software like GopherTrunk, raw samples arrive as a continuous stream of IQ data and pass through stage after stage: a band-pass filter, a frequency shift, decimation, demodulation, decoding. That is dataflow — a perfect fit for functional thinking. Each stage is naturally a pure transformation: samples in, samples out, no hidden state to corrupt the buffer. Modelling a DSP chain as composed pure functions makes each stage independently testable and safe to parallelise across cores, a theme we revisit in concurrency models. You will still use imperative loops for the tight inner number-crunching and objects to represent a tuned receiver — which is exactly the multi-paradigm reality.

Recap

  • Paradigms are styles, not languages — imperative, OOP, functional and declarative are ways to organise and reason about code.
  • Imperative/procedural — step-by-step instructions over mutable state; closest to the hardware.
  • Object-oriented — bundle state and behaviour into objects; prefer composition over deep inheritance.
  • Functional — compose pure, side-effect-free transformations; immutable data by default.
  • Declarative — describe what you want (SQL, logic, config) and let the system find how.
  • Most languages are multi-paradigm — Python, Rust and Go let you mix styles; dataflow pipelines map cleanly onto functional composition.

Next up: how the same source code becomes something the machine can run — the difference between compiled, interpreted and JIT execution.

Frequently asked questions

What is a programming paradigm?

A paradigm is a style of organising code and reasoning about computation — for example imperative (step-by-step instructions), object-oriented (bundles of state and behaviour), functional (composing pure transformations), or declarative (describe the result, let the system find the steps).

Are most modern languages object-oriented or functional?

Most are multi-paradigm. Python, Rust, JavaScript and even Go let you write procedural, object-style and functional code in the same file. The paradigm is a way you choose to think, more than a rule the language forces.

Why does functional style suit signal processing?

Signal processing is mostly data flowing through transformations — filter, mix, decimate, demodulate. That is exactly what functional composition models: pure functions chained into a pipeline, each output feeding the next, with no hidden shared state to corrupt the stream.