skip to content

Paradigm Families

A comparative map of how programming styles differ — in state, control, evaluation and composition — and which problems each one fits. Interviewers ask so you justify a style, not a favourite.

on this pageshow

explore

questions

page 1 of 2

On the state axis of paradigm classification, what are you measuring when you place an unfamiliar language?

level: juniorimportance: must knowfreq 66%

answer

  1. not whether assignment exists
  2. reach of a write, not its presence
  3. default binding versus opt-in mutation
  4. can a callee change the caller's value
  5. who else can observe the write

basics

~20 s

The state axis measures how far a write travels, not whether assignment exists. You are asking whether a step overwrites a value other code still holds, or produces a new value and leaves the old one intact.

solid answer

~50 s

Nearly every language that ships real systems can mutate something, so the presence of assignment classifies nothing. What the axis measures is **reach and default**: when a routine writes, can another holder of that value observe the write, and is that the language's default or an opt-in the code has to ask for. To place an unfamiliar language from a sample I read three things - whether a callee can change what the caller passed it, whether a second assignment to a bound name is even legal, and whether the built-in collection operations hand back a new value or edit the one you had. A language where writes are confined and deliberate sits toward the immutable end even though it has assignment; one where any holder of a reference can be surprised by another's write sits at the mutable end.

code

pseudocode · 14 lines
pseudocode
function applyDiscount(order, percent)
    order.total = order.total - order.total * percent
    return order

function withDiscount(order, percent)
    return copyOf(order, total = order.total - order.total * percent)

cart = makeOrder(total = 100)

applyDiscount(cart, 0.1)
// cart.total is now 90 - the caller's value was rewritten

newer = withDiscount(cart, 0.1)
// cart.total is still 90; newer holds 81

go deeper

for a junior

Be able to say the axis is about whether a write can be seen by other code that holds the same value, and give one example of a routine changing what its caller passed in.

for a middle

Explain the three readings - pass-and-mutate, rebinding, and what collection operations return - and say why the standard library's shape is stronger evidence than any single declaration keyword.

for a senior

Show what the score predicts in production: how many places you must read to explain a wrong value, and what a shared mutable value costs a team in review discipline it cannot check.

for a principal

Frame the score as a cost you are choosing: language-enforced confinement of writes versus convention-enforced, and what each buys across a codebase that outlives the people who wrote it.

## What the state axis measures Paradigm families differ first in how they treat state, and the axis that captures it is not "does this language have variables" but **how far a write travels and who can observe it**. A write into a value nobody else holds is invisible to the rest of the program. A write into a value three other places hold is a message delivered to all three without their asking. Almost everything this axis predicts - whether a routine can be explained by reading it alone, how much of the program you must read to explain a wrong value, how much a caller has to trust a callee - follows from that one measurement. Three properties make up the score: - **Default binding** - once a name is bound, may it be bound again without the code asking for permission? - **Depth** - is only the *name* fixed, with a freely editable value behind it, or is the value itself closed to writes? - **Sharing** - when a value is passed or stored, do both sides end up holding the same thing, so that one side's write is the other side's surprise? ## Why "does it have assignment" is the wrong test Every language used for production work can mutate something: a buffer, a cell, a field, an accumulator inside a tight loop. If the presence of assignment decided the score, every practical language would land on the same point and the axis would classify nothing. What varies enormously is the **size of the region a write can reach**, and whether reaching further is the default or a deliberate act the code has to spell out. At one end, every binding is fixed and every value is closed: producing a changed version means producing a new value, and whoever kept the old one still has exactly what they had. At the other end, any holder of a reference may write through it, and the only way to know whether your value is still what you left is to audit everyone who could have touched it. Most languages sit somewhere between, and **where** they sit is the score you write down. ## Three readings that place a language from a sample 1. **The pass-and-mutate reading.** A routine is handed a value and returns nothing useful. Did the caller's value change? If a callee can edit what you gave it, writes cross call boundaries by default. 2. **The rebinding reading.** Does a second assignment to an already-bound name compile, and is the declaration form the sample reaches for a rebindable one or a fixed one? The form the code uses by habit is the language's real opinion. 3. **The collection reading.** Does adding an element return a new collection, or edit the one you had and return nothing? Standard-library shapes are the strongest evidence, because they are what most code in that language will do most of the time. ## What each end buys and costs | Reading | Mutable-leaning end | Immutable-leaning end | |---|---|---| | A callee that takes your value | may change what you still hold | can only hand back something new | | Explaining a wrong value | read every holder that could have written | read the one expression that produced it | | Sharing a value between two parts | free, and risky | free, and safe | | Producing a changed version | cheap, in place | allocates, often sharing the unchanged parts | | Where the discipline lives | in conventions and review | in the language, and checked | The mutable end buys locality of **cost**: an update touches what it must and nothing else. The immutable end buys locality of **reasoning**: a value you hold cannot be edited behind your back, so a routine can be understood from its own text. Neither is free. The axis exists so you can say which price a codebase is already paying, rather than which name the language goes by. ## Where the axis stops The state axis scores a **language's defaults**, and three things it does not tell you are worth stating plainly: - It does not say how much a given program mutates. Code can keep mutation inside a small region whatever the default is; the axis places the language, and reading the code is a second, separate act. - It does not mean nothing in memory changes at the immutable end. Memory is written constantly; the claim is about what the *program* can observe through the values it holds. - It does not settle the other axes. A language can be immutable-leaning and still fix the order of every step, or mutable-leaning and still let the evaluator reorder work that none of those writes constrain. Score the axis from the sample, write the score down, and move to the next axis.

  • Two languages both allow mutation. What can still separate them on the state axis?
    Defaults and reach. Ask whether the ordinary declaration form is rebindable or fixed, whether a callee can write through a value it was handed, and whether the language gives you a way to *guarantee* it cannot. A language where mutation is opt-in, locally marked and unshareable scores far from one where any holder may write.
  • A value is declared as a constant but its contents can still be edited. What does that do to the score?
    It fixes the name, not the value, so it moves the language very little. The axis measures whether a write is observable to other holders; a fixed name with an editable payload still lets a callee change what the caller holds. Depth of immutability is what the reading is after, not the declaration keyword's promise about rebinding.

saying these in an interview costs you the question

  • Claims any language with assignment is mutable on every axis
  • Says immutability means nothing in memory ever changes
  • Confuses a fixed name binding with a value closed to writes
  • Assumes immutable style is always slower because it copies
  • Places a language by its self-description instead of its defaults
open as a page

Why does a chat room's shared mutable participant roster force coordination between delivery threads when an immutable roster does not?

level: juniorimportance: must knowfreq 70%

basics

~20 s

A mutable roster is changed in place, so every reader and every writer must follow one shared discipline or observe a half-applied change. An immutable roster is never written after publication, so a reader that holds one needs no permission from anybody.

open as a page

In a declarative description of desired state, what does stating the result rather than the steps buy you?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Stating the result hands the evaluator the strategy: it derives which operations run, in what order, and whether any are needed. You gain re-runnability, ordering freedom and portability, and give up step-by-step control of how the work is done.

open as a page

In an imperative simulation that assigns each entity a new position every tick, what becomes of the previous position?

level: juniorimportance: must knowfreq 74%

basics

~20 s

Assignment in the imperative model is a destructive update: the storage cell that held the old position now holds the new one. The previous value survives only if the program copied it somewhere before the write.

open as a page

A payroll subroutine changes the pay amount it was given, but the caller still sees the old value — which parameter-passing mode is in use?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Call by value: the routine received a copy of the amount, so its assignment updated only the copy in its own activation record. Call by reference, or returning the new value, would make the update visible to the caller.

open as a page

On the control-versus-data-flow axis, what in a short code sample tells you which side a language sits on?

level: middleimportance: must knowfreq 55%

basics

~20 s

Whether the text fixes the order or only the dependencies. Control flow names the next step and relies on what the previous one left behind; data flow names which values feed which, leaving the evaluator free to order anything the dependencies do not constrain.

open as a page

Each chat room runs as an isolated unit with its own mailbox — what does that model remove, and what does it not?

level: middleimportance: must knowfreq 58%

basics

~20 s

Isolation removes shared state: one unit owns a room's roster and handles one message at a time, so its body is ordinary sequential code. It does not remove ordering between senders, overload, or the need to exchange messages to reach another unit's state.

open as a page

When a constraint solver builds a support rota, what does propagation do to the variables' domains before each guess?

level: middleimportance: must knowfreq 55%

basics

~20 s

Propagation deletes, from each domain, values that no constraint can support given the choices already made, and repeats until nothing more can be removed. It prunes whole subtrees cheaply, and an emptied domain proves the current branch is dead.

open as a page

In a recalculation sheet where every cell is defined by the cells it reads, what decides evaluation order?

level: middleimportance: must knowfreq 62%

basics

~20 s

The dependency graph decides, not the order the definitions were written. Each cell is a node with an edge from every cell it reads, and the engine evaluates in topological order, recomputing what an edit made stale.

open as a page

What must an engine applying a declarative description do when the desired state already holds?

level: middleimportance: must knowfreq 58%

basics

~20 s

It must still read reality, compare it with the description, find no gap, and perform no writes. That no-op is idempotence, and it is what lets one description serve as the create path, the repair path and the drift check.

open as a page

Why can one containment rule answer both 'what does this assembly contain?' and 'what contains this bolt?'

level: middleimportance: must knowfreq 58%

basics

~20 s

Because a rule states a relation rather than a function. Its arguments are logical variables that unification can bind on either side, so whichever argument you leave unbound is the one the engine searches for.

open as a page

Your pricing core is pure functions over immutable values while the storage layer returns mutable order records - what does that seam cost?

level: middleimportance: must knowfreq 62%

basics

~20 s

Every crossing pays conversion: you translate between two representations of one order, copy deeply enough that neither side can edit the other's data, and keep both shapes in step whenever the model changes. Per-crossing work plus permanent duplication.

open as a page

A second worker runs the same tick loop over one mutable entity grid; why is the result wrong even without a crash?

level: seniorimportance: must knowfreq 66%

basics

~20 s

An imperative step is a read, a computation and a write, and its correctness assumes nothing changes the state in between. Two workers interleave those steps, so updates are lost and half-updated records become observable — while every individual write stays legal.

open as a page

Your containment knowledge base answers 'no' for a part nobody has recorded yet - what assumption did that answer make?

level: seniorimportance: must knowfreq 54%

basics

~10 s

The closed-world assumption: anything not derivable from the recorded facts is treated as false. The engine proved nothing about that part - it merely failed to prove containment, and reported failure as a negative.

open as a page

For a small nightly batch tool over one record shape, what argues for plain subroutines instead of an object model?

level: seniorimportance: must knowfreq 62%

basics

~20 s

Plain subroutines win where the record shape is fixed and shared, variation points are few, and the whole run reads top to bottom. An object model charges indirection for substitutability a one-shape batch tool never exercises.

open as a page

In a constraint model of a quarterly support rota, what three parts must you declare before a solver can search?

level: juniorimportance: should knowfreq 45%

basics

~20 s

Decision variables, one per choice the roster must contain; a finite domain of allowed values for each; and constraints that rule combinations out. The model states no algorithm - the solver owns the search over that space.

open as a page

What does a whole-column summary over a column of readings buy over an explicit element-by-element loop?

level: juniorimportance: should knowfreq 50%

basics

~20 s

It removes the traversal from your code: with no index there is no off-by-one, no accumulator to initialise, no bound to get wrong. The operation names the shape of the data and leaves the evaluator free to choose how to walk it.

open as a page

In a facts-and-rules knowledge base of which component contains which, what does a rule give you that more facts cannot?

level: juniorimportance: should knowfreq 46%

basics

~20 s

A rule is a general implication the engine applies to whatever facts exist, so it derives answers nobody stored - including containment at depths nobody enumerated. A fact states exactly one instance and generalises to nothing.

open as a page

On the composition-and-reuse axis, what unit are you trying to identify in an unfamiliar language?

level: middleimportance: should knowfreq 44%

basics

~20 s

The thing the language lets you name, hand to other code, and join to another of its kind without editing either. Whatever fits - a routine, a bundle of behaviour, a module, a rule - is that language's unit of reuse.

open as a page

A chat server replaces the whole room roster on each join instead of mutating it — what does that cost?

level: middleimportance: should knowfreq 55%

basics

~20 s

Each join builds a successor to an n-entry roster, so the style pays copying and allocation proportional to roster size times change rate, it still needs one agreed writer, and readers work from the version they captured.

open as a page

Why can two runs of the same declarative desired-state description do the work in different orders?

level: middleimportance: should knowfreq 48%

basics

~20 s

Order is not written in the description: the engine derives it from declared dependencies and may sequence anything they leave unconstrained. Two runs still agree because independent work commutes, provided every real dependency was declared.

open as a page

Two names in a tick loop refer to the same entity record; what surprises a reader when one of them is written?

level: middleimportance: should knowfreq 58%

basics

~20 s

The write is visible through the other name, which the statement never mentioned. Aliasing makes a statement's footprint wider than its text, so reading the statement alone no longer tells you what state it changed.

open as a page

Two adjacent statements in an imperative tick loop are swapped and the simulation changes; what property makes order part of the meaning?

level: middleimportance: should knowfreq 55%

basics

~20 s

Each statement's meaning is defined against the state the previous statements left behind. Sequencing is how imperative programs compose, so two statements that touch the same location are pinned in place and swapping them changes the result.

open as a page

When a containment query's first matching clause leads to a dead end, what does the inference engine do next?

level: middleimportance: should knowfreq 41%

basics

~10 s

It backtracks: the engine returns to the most recent choice point, undoes every variable binding made since, and tries the next matching clause - repeating until a branch succeeds or the alternatives run out.

open as a page

When one language supports several styles, on what basis do you choose one for a given module rather than the whole repository?

level: middleimportance: should knowfreq 50%

basics

~20 s

Choose on the data's properties, not preference: things with identity and a lifecycle suit objects holding state, transformations of plain values suit pure functions, fast-changing rules suit a description one evaluator reads. Decide per module - every switch adds a seam.

open as a page

A payroll routine recursing down a long management chain exhausts the call stack — what does each nested call push onto it?

level: middleimportance: should knowfreq 52%

basics

~20 s

Each nested call pushes an activation record: the return address, the saved caller bookkeeping, the parameter copies and the routine's local variables. Stack use is call depth multiplied by frame size, so a long chain exhausts the region.

open as a page

You decomposed a payroll run top-down into nested subroutines — what signals that the decomposition has stopped paying for itself?

level: middleimportance: should knowfreq 42%

basics

~20 s

Decomposition stops paying when a split only relocates code: routines called once whose names restate their bodies, parameters threaded through layers that never read them, and any new field requiring an edit at every level.

open as a page

Why does scoring an unfamiliar language on each axis beat labelling it with a single paradigm name?

level: seniorimportance: should knowfreq 48%

basics

~20 s

A paradigm name is a cluster of axis positions that often travel together, so it under-determines every one of them. Axis scores are checkable against a sample and each answers a concrete question about the code you have to maintain.

open as a page

A product rule caps a user at three chat rooms at once, and each room is an isolated unit owning only its roster — why is that rule expensive to enforce here?

level: seniorimportance: should knowfreq 45%

basics

~20 s

The rule counts across rosters no single unit can see, so isolation does not remove the invariant, it relocates it. Enforcing it needs an owner for the whole fact, which brings back a contention point, or late repair after a violation window.

open as a page

Your constraint model of the support rota reports infeasible and returns nothing - how do you find which constraints conflict?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Shrink the model instead of reading it. Drop constraints one at a time and re-solve, keeping only those whose removal restores feasibility. The minimal conflicting set that survives names the handful of rules to renegotiate.

open as a page

showing 1–30 of 44