skip to content

Classification Axes

What separates one style from another: how state is treated, how control moves, when expressions are evaluated, and what composes. Interviewers use the axes to place an unfamiliar language.

on this pageshow

questions

5

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

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

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

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

On the evaluation axis, what can you no longer conclude from where an expression appears in the text?

level: middleimportance: nice to knowfreq 33%

basics

~20 s

When - or whether - the work happens. Under deferred evaluation, writing an expression only records how to produce a value; the work runs at the point some other code demands it, which may be far away or never.

open as a page