The Station and the Struct

There is a design system in architecture called kit-of-parts. The idea is straightforward: rather than designing every element of a building from scratch, you define a catalogue of pre-engineered components (ceiling panels, structural bays, platform furniture, wall cladding systems, fixing brackets) each with known properties and defined connection points. You then compose a building by selecting and assembling from that catalogue.

It was used to notable effect on the Jubilee Line Extension in the late 1990s. Roland Paoletti, the project's chief architect, established a shared palette of structural conventions and systems (the raw concrete aesthetic, the ceiling void dimensions, the duct routing logic) and then commissioned individual architects for each station, leaving them to compose from within those constraints. The result is a family of stations that are clearly related without being identical. Canary Wharf is a completely different design to the Jubilee line station of Waterloo, but they share an unmistakable language.

When I first encountered the term, my instinct was to reach for a software analogy: object-oriented design. Reusable components, self-contained units, a kind of taxonomy. As it turns out, I am not alone in that instinct. The Wikipedia article on kit-of-parts opens with exactly that framing: "Kit-of-parts theory refers to the study and application of object-oriented building techniques." But I think the analogy is wrong, and the correct one it considerably more interesting. You can tell it is wrong, because the rest of the article's content argues against it.

§ Why OO is the wrong analogy

Object-oriented design is fundamentally about taxonomy and inheritance. A PlatformCanopy is-a RoofElement, which is-a StructuralComponent. It inherits properties from its parent class and potentially overrides behaviours. The relationship between components is one of lineage.

Kit-of-parts thinking is nothing like this. A platform canopy does not extend AbstractRoofElement. It works in a given position because it satisfies a set of constraints: it fits within a defined structural envelope, its fixing geometry matches the substrate, it meets the load specification. The question is not what family does this belong to but does this satisfy the interface for this position.

This might sound like splitting hairs, but it is a crucial distinction in the way we map the world. OO couples components to a hierarchy. Kit-of-parts decouples components from everything except the constraints they must satisfy. A canopy from one manufacturer and a canopy from another are interchangeable not because they share a parent class, but because they both meet the spec.

The Wikipedia article itself describes "standard, simple connections between assemblies," a "parts library," and components as "instances of a master element". None of that implies inheritance or taxonomy. It implies composition and constrain satisfaction. The OO label is borrowed from software without quite fitting what is actually being described.

§ The Rust model

I know I am a big Rust fanboy, but it provides a better software analogy in its type and trait system.

In Rust, data and behaviour are deliberately separated. You define a struct or an enum to describe the shape of your data, and separately you define traits to describe capabilities. A type gains a capabilities by implementing the relevant trait. Nothing is inherited. Nothing is bundled into a hierarchy.

trait Loadbearing {
  fn max_load_kg(&self) -> f64;
  fn fixing_points(&self) -> Vec<FixingPoint>;
}

struct PlatformCanopy {
  span_mm: u32,
  material: Material,
}

impl Loadbearing for PlatformCanopy {
  fn max_load_kg(&self) -> f64 { 500.0 }
  fn fixing_points(&self) -> Vec<FixingPoint> { /* ... */ }
}

A function that needs a load bearing component does not ask for a specific type. It asks for any time that satisfies the Loadbearing trait:

fn install_overhead<T: Loadbearing>(component: &T, position: Position) {
  // works for any component that satisfies the constraint
}

This is a constraint satisfaction, not taxonomy. It is interchangeability through interface, not through lineage. It is, in other words, exactly the logic of kit-of-parts.

§ Illegal states and illegal configurations

There is another idea from type-driven Rust design that maps cleanly onto architecture: making illegal states unrepresentable. The principle, borrowed from the ML family of languages, holds that your type system should be expressive enough that configurations which make no sense cannot be constructed at all. The compiler rejects them before anything runs.

Good kit-of-parts design does the same thing physically. Components have asymmetric fixing points so they cannot be installed upside down. Electrical conduit housings have keyed connectors so incompatible voltages cannot be wired together. The interface itself encodes the constraint.

In Rust:

struct Metres(f64);
struct Kilonewtons(f64);

// You cannot accidentally pass a load where a length is expected.
// The types are distinct even though both wrap f64;

fn check_span(span: Metres, capacity: Kilonewtons) { /* ... */ }

The newtype pattern (wrapping primitives in semantically distinct types) is a direct analogue of the keyed connector. Two things that are representationally identical (both are numbers) are made distinct at the type level so that confusing them is a compile error rather than a runtime failure.

§ Composition as the primary design act

In both kit-of-parts architecture and Rust, the primary design act is composition. You are not inventing new things from scratch at each step; you are selecting components that satisfy the constraints of a given position and assembling them into a coherent whole.

This is a different creative mode from either bespoke design (where everything is custom) or OO inheritance (where you extend and override). It demands a different upfront investment (defining the catalogue, specifying the interfaces, working out what connection points actually are) but it pays dividends in reusability, replaceability, and the ability to reason locally about each component without holding the entire system in your head.

A Rust programmer who has defined clean trait boundaries can swap one implementation out for another without touching anything else, because the constraint is the only coupling. A maintenance engineer at Canary Wharf who needs to replace a ceiling panel does not need to understand the entire station; they need to know the fixing spec.

§ The deeper point

What kit-of-parts and Rust share is a particular philosophy about where complexity should live. Not in the connections between things (those should be as simple and well-defined as possible) but in the things themselves, which can be as sophisticated as they need to be internally, provided they honour their interfaces.

OO tends to let complexity leak into the connections. Inheritance hierarchies create implicit dependencies up and down the chain; a change to a base class ripples out in ways that can be hard to predict. The taxonomy becomes load-bearing in ways it was never designed to be.

Kit-of-parts, like Rust's trait system, keeps the connections honest. The interface is the contract. Satisfy the contract, and you belong in the catalogue. Fail it, and you do not, and the system tells you so before you have bolted anything together.

There is something quietly satisfying about finding the same design insight arrived at independently by architects building transport infrastructure and language designers building a systems programming language. The problem (how do you compose large, complex things reliably from smaller pieces) turns out to have a similar shape regardless of whether the pieces are made of concrete and steel or bits and bytes.