Skip to main content

Mathematical Structure in Software Design

The best software I’ve written shares something with good mathematics. Not because it uses advanced math, but because it embodies the same principles: abstraction, composition, and invariants.

What I Mean by Mathematical Structure

In mathematics, good work has certain properties:

Generality: One theorem covering many specific cases. That reveals structure.

Composability: Small results combine to prove larger results.

Invariants: Properties preserved across transformations.

Minimal assumptions: Maximum results from minimum prerequisites.

Inevitability: Each step follows naturally from the previous.

These same properties make software better.

Generality in Code

Good abstractions are general.

Instead of:

def sum_integers(lst): ...
def sum_floats(lst): ...
def sum_complex(lst): ...

Write:

def sum(iterable): ...  # Works for any numeric type

This isn’t just code reuse. It’s recognizing essential structure independent of representation.

Mathematical parallel: group theory doesn’t care if you’re rotating shapes or permuting elements. It cares about the structure of symmetry itself.

Composability

Mathematical proofs compose: lemmas build into theorems build into theories.

Software should work the same:

def normalize(vector):
    return vector / magnitude(vector)

def magnitude(vector):
    return sqrt(sum(x**2 for x in vector))

# Compose naturally
unit_vector = normalize(data)

Each function does one thing. They combine predictably.

This is Unix philosophy through a mathematical lens.

Invariants as Correctness

In math, invariants let you reason about properties that survive transformations.

In code, invariants are contracts that must hold:

  • A sorted list stays sorted under these operations
  • This hash function is deterministic
  • This pure function returns same output for same input
  • This encryption preserves confidentiality

Design APIs to make important invariants obvious and enforceable.

Type systems help:

-- Once encrypted, type system won't let you use as plaintext
encrypt :: PlainText -> Key -> CipherText
decrypt :: CipherText -> Key -> PlainText

Minimal Assumptions

Mathematical theorems state assumptions explicitly: “Let X be a metric space with property P…”

Software should too:

Type signatures declare valid inputs:

def cosine_similarity(v1: np.ndarray, v2: np.ndarray) -> float:
    """
    Precondition: v1 and v2 are non-zero vectors of same dimension
    Returns: similarity in [-1, 1]
    """

Documentation states what must be true:

  • Preconditions: what the caller must ensure
  • Postconditions: what the function guarantees
  • Invariants: what always holds

Make it hard to violate assumptions accidentally.

Inevitability in Design

The best mathematical proofs feel inevitable. Each step follows naturally.

The best software feels the same:

Function names that make usage obvious:

# Not: process_data(x, True, False, 3)
# But:
results = data.filter(predicate) \
              .map(transform) \
              .reduce(aggregator)

Type systems that guide toward correct code:

// Rust's ownership system makes it hard to write memory-unsafe code
// The correct way is the natural way

APIs where the right way is the natural way:

# Natural usage:
with open(filename) as f:
    data = f.read()  # Automatically closes, handles errors

When users think “of course, what else would it be?” you’ve got it.

Two Degrees, One Design Sense

My two master’s degrees gave me complementary modes of thinking:

Computer Science (2015): How to build systems that work

  • Algorithms and data structures
  • Systems programming
  • Distributed computing
  • Practical engineering

Mathematics (2023): How to reason about structure and correctness

  • Rigorous proofs
  • Abstract algebra
  • Measure theory
  • Mathematical maturity

The combination matters. You can build systems with mathematical structure, and that structure pays off.

Examples From My Work

Statistical libraries:

Functions compose like mathematical operations:

bootstrap(estimator, data, resamples = 1000) %>%
  confidence_interval(level = 0.95)

Returns a distribution you can query. Each piece does one thing. They compose naturally.

Cryptographic tools:

Invariants enforced by types:

// Encrypted<T> can't accidentally be used as T
// Type system prevents confusion
template<typename T>
class Encrypted { ... };

Network analysis:

Abstract over graph representations:

def pagerank(graph: GraphProtocol, damping=0.85):
    """Works on any graph satisfying the protocol"""

Write algorithms that work on any structure satisfying the interface.

Why This Matters

Mathematical structure in code isn’t aesthetic preference. It’s engineering for comprehension.

Code with this structure is:

Easier to reason about: Invariants guide understanding

Easier to test: Composability enables isolated testing

Easier to extend: Generality means fewer special cases

Easier to prove correct: Invariants support formal reasoning

Easier to maintain: Clear structure resists decay

The Discipline

Achieving this requires discipline:

Resist special cases: Factor out common structure instead

Name things precisely: Names should reveal intent

State assumptions clearly: Document preconditions and invariants

Make properties obvious: Important characteristics should be explicit

Compose small pieces: Build complex from simple

It’s harder than throwing code together. But the result is software that lasts.

Practical Implications

For my research:

Reliability analysis code: Estimators defined abstractly, work for any censoring pattern

Network analysis tools: Graph algorithms independent of representation

Statistical methods: Mathematical properties preserved in implementation

For general development:

Write functions that compose: Small, focused, predictable

Use types to encode invariants: Let the compiler help maintain correctness

Document the mathematics: Explain theoretical properties of your code

Test invariants explicitly: Verify properties that should always hold

The Legacy Question

With stage 4 cancer, I think about code differently.

Will someone else understand this design? Are the mathematical principles clear? Can this be extended without me? Does the structure communicate intent?

Mathematical structure helps here. Code structured like a proof, stating its assumptions, proceeding logically, reaching inevitable conclusions, verifiable independently, is more continuable than clever hacks.

Recognizing Structure

Monoids everywhere:

# Recognizing monoidal structure
# Identity element, associative operation
[].extend([1,2]).extend([3,4])  # List concatenation
0 + 5 + 10  # Addition
1 * 5 * 10  # Multiplication
"" + "hello" + "world"  # String concatenation

Functors in practice:

# map preserves structure
list.map(f).map(g) == list.map(lambda x: g(f(x)))
Option.map(f).map(g) == Option.map(lambda x: g(f(x)))

Type algebra:

// Sum types: A | B (union)
// Product types: {a: A, b: B} (record)
// Function types: A => B
// These compose algebraically

Recognizing these patterns makes code comprehensible at a higher level.

What I’m Aiming For

Every library I publish aims for:

Mathematical clarity: Structure reflects underlying theory

Compositional design: Small pieces combine naturally

Explicit invariants: Important properties are obvious

Minimal assumptions: Work with maximum generality

Inevitable usage: The right way feels natural

This takes time. But it’s how you build things that last.

The Bottom Line

CS taught me how to build. Math taught me how to reason about what I build.

The combination:

  • Code that composes
  • Types that enforce correctness
  • Abstractions that reveal structure
  • Invariants that guide reasoning
  • Designs that feel inevitable

Cancer doesn’t change this. If anything, it sharpens it: do work that’s comprehensible, continuable, and correct.

Mathematical structure serves all three.

Discussion