Formal Methods

Closure, Totality, and the Algebra of Safe Systems

Why three mathematical properties can determine whether your system fails safe or fails silent

Published
January 08, 2026 21:20
Reading Time
12 min
Closure, Totality, and Boundedness Properties Diagram

Note: This article discusses mathematical properties relevant to safety-critical system design. Actual safety implementations require rigorous verification, domain expertise, and regulatory approval. This is not engineering guidance for specific applications.

When a safety-critical system encounters an unexpected condition, it has two choices: fail safe or fail silent. The difference between these outcomes—between a controlled shutdown and an undetected malfunction—often comes down to three mathematical properties that most engineers never explicitly consider.

These properties are closure, totality, and boundedness. Together, they form a kind of algebra for reasoning about system safety. Understanding them transforms safety engineering from intuition and testing into something closer to proof.

The Problem with “Mostly Works”

Consider a function that calculates a running average. It works correctly for typical inputs. But what happens when:

  • The input is NaN or infinity?
  • The function is called recursively by an interrupt handler?
  • Memory pressure causes the historical buffer to fail allocation?
  • The counter overflows after months of continuous operation?

Each of these represents an edge case that “mostly works” code ignores. In a web application, such failures might cause a page refresh. In a pacemaker, they could cause silence—the most dangerous failure mode of all.

The traditional response is defensive programming: add checks, handle exceptions, write more tests. But this approach is fundamentally reactive. We’re patching holes as we discover them, never confident we’ve found them all.

Design Property: Enumerable Failure Modes

A system with closure, totality, and boundedness has failure modes that can be enumerated at design time, not discovered in production.

Property 1: Closure

A system is closed if its next state depends only on its current state and current input—nothing else.

Formally: St+1 = f(St, xt)

This seems obvious until you catalogue what violates it:

  • Global variables — state depends on values modified elsewhere
  • History buffers — state depends on arbitrarily old inputs
  • System calls — state depends on OS scheduler, file system, network
  • Timestamps — state depends on wall-clock time
  • Random numbers — state depends on entropy source

A closed system has no hidden inputs. Every dependency is explicit in the state vector. This means behaviour is reproducible: given the same initial state and input sequence, the system produces identical outputs every time.

Why does this matter for safety? Because reproducibility enables diagnosis. When a closed system fails, you can replay the exact sequence that caused the failure. When a non-closed system fails, you’re left wondering what hidden variable was different this time.

Closed system:
  State = (counter, accumulator, fault_flag)
  Input = observation
  Next state = deterministic function of state + input
  
Non-closed system:
  State = (counter, accumulator, fault_flag, ???)
  Input = observation + time + thread state + heap layout + ...
  Next state = ¯\_(ツ)_/¯

Achieving Closure

Closure is a design constraint, not a runtime property. You achieve it by:

  1. Embedding configuration at initialisation — don’t read config files during operation
  2. Eliminating buffers — use streaming algorithms that maintain fixed-size state
  3. Removing system dependencies — no clocks, no file I/O, no network calls in core logic
  4. Making all inputs explicit — if something affects output, it must be a parameter

The EMA vs SMA analysis demonstrates this concretely: exponential moving averages maintain closure (fixed state size) while simple moving averages violate it (unbounded history buffer).

Property 2: Totality

A function is total if it produces a valid output for every possible input. The opposite—a partial function—is undefined for some inputs.

Most software is partial. Division is undefined when the denominator is zero. Array access is undefined when the index is out of bounds. Pointer dereference is undefined when the pointer is null. These “undefined behaviours” are gaps in the function’s domain where anything can happen.

In safety-critical systems, partial functions are silent failure modes. The function doesn’t return an error—it simply doesn’t return a meaningful result. Or it corrupts memory. Or it hangs. The caller has no way to know something went wrong.

Partial Function
  • Undefined for some inputs
  • May crash, hang, or corrupt
  • Caller cannot detect failure
  • Requires defensive checks everywhere
Total Function
  • Defined for all inputs
  • Returns valid result or explicit error
  • Failure is always signalled
  • Safety properties are composable

Achieving Totality

Totality requires explicitly handling every input case:

hb_result_t hb_step(hb_fsm_t *m, hb_input_t input) {
    hb_result_t result = { .state = HB_FAULT };
    
    // Handle invalid state
    if (m == NULL) {
        return result;  // Total: null input handled
    }
    
    // Handle reentrancy
    if (m->in_step) {
        m->fault_reentry = 1;
        m->state = HB_FAULT;
        return result;  // Total: reentrant call handled
    }
    m->in_step = 1;
    
    // Handle invalid input
    if (input != HB_BEAT && input != HB_TIMEOUT) {
        m->fault_input = 1;
        m->state = HB_FAULT;
        m->in_step = 0;
        return result;  // Total: unknown input handled
    }
    
    // Normal processing...
    // Every path returns a valid result
    
    m->in_step = 0;
    return result;
}

Notice the pattern: every possible input has an explicit handling path. There are no gaps where behaviour is undefined. The function is total over its entire domain.

For floating-point inputs, totality requires explicit handling of special values:

if (!isfinite(value)) {
    m->fault_fp = 1;
    m->state = FAULT_STATE;
    return error_result;  // Total: NaN and Inf handled
}

Property 3: Boundedness

A system is bounded if it uses constant memory and constant time, regardless of input history or operational duration.

  • O(1) memory: State size is fixed at design time
  • O(1) time: Each step completes in bounded cycles

Boundedness rules out:

  • Dynamic allocation — malloc can fail, fragment, or exhaust
  • Recursion — stack depth depends on input
  • Variable-length loops — iteration count depends on data
  • Growing buffers — memory usage increases over time

Why does boundedness matter? Because unbounded resource usage is a time bomb. A system that allocates memory on each event will eventually exhaust available memory. A system with recursion depth dependent on input can overflow the stack. These failures might take months to manifest—long after testing is complete.

Design Property: Time-Independent Safety

A bounded system's safety properties hold on day one and day one thousand. No accumulation of state can cause delayed failure.

Achieving Boundedness

Boundedness is achieved through algorithm selection and implementation discipline:

  1. Use streaming algorithms — EMA instead of SMA, Welford’s instead of batch variance
  2. Pre-allocate everything — all memory reserved at initialisation
  3. Fixed iteration bounds — loops have constant maximum iterations
  4. Tail-call elimination or iteration — no recursive algorithms

The state machine pattern naturally supports boundedness. An FSM with n states and m transitions uses O(1) memory and processes each input in O(1) time, regardless of how many inputs it has processed historically.

The Algebra of Composition

The power of these properties emerges when composing systems. If module A and module B are both closed, total, and bounded, then their composition A → B is also closed, total, and bounded.

This compositional guarantee is rare in software engineering. Usually, combining correct components can produce incorrect systems due to emergent interactions. But closure, totality, and boundedness are preserved under composition.

Module A: closed, total, bounded
Module B: closed, total, bounded

A → B composition:
  - Closed: output of A depends only on A's state + input
           input of B is output of A
           therefore B's state depends only on B's state + A's output
           composition is closed ✓
           
  - Total: A produces valid output for all inputs
          B produces valid output for all inputs (including A's outputs)
          composition is total ✓
          
  - Bounded: A uses O(1) memory and time
            B uses O(1) memory and time
            composition uses O(1) + O(1) = O(1) ✓

This is why the c-from-scratch methodology emphasises these properties at the module level. Each module—the heartbeat detector, the baseline monitor, the anomaly classifier—is independently closed, total, and bounded. Composition then inherits these properties automatically.

Determinism as a Consequence

When a system has closure, totality, and boundedness, determinism follows as a consequence:

  • Closure ensures no hidden inputs affect behaviour
  • Totality ensures every input produces a defined output
  • Boundedness ensures resource usage cannot cause variable behaviour

Given identical initial states and input sequences, such a system produces identical outputs. This reproducibility is the foundation of testability, debuggability, and certifiability.

The relationship to safety certification frameworks becomes clear. Standards like IEC 62304 for medical devices require evidence that software behaves predictably under all conditions. A system with closure, totality, and boundedness provides exactly this evidence—not through exhaustive testing, but through structural properties that can be verified by inspection.

Practical Implications

These three properties impose constraints that feel restrictive at first:

  • No global state
  • No dynamic allocation
  • No partial functions
  • No unbounded loops
  • No system calls in core logic

But these constraints are clarifying, not limiting. They force explicit design decisions that would otherwise be implicit (and often wrong). They make failure modes enumerable rather than discoverable. They transform “I think it works” into “I can show it works.”

The trade-off is design effort. Achieving closure, totality, and boundedness requires thinking carefully about every input, every state transition, every resource allocation. This effort is front-loaded—spent during design rather than during debugging.

For safety-critical systems, this trade-off is favourable. The cost of careful design is measured in engineering hours. The cost of silent failure is measured in lives.

Conclusion

Closure, totality, and boundedness are not just theoretical properties—they are practical design constraints that determine whether systems fail safe or fail silent.

  • Closure ensures reproducibility: same inputs, same outputs, every time
  • Totality ensures explicit failure: no undefined behaviour, no silent corruption
  • Boundedness ensures temporal stability: safety properties that hold indefinitely

Together, they form an algebra for reasoning about safety. Systems built on these foundations can be composed confidently, tested meaningfully, and certified with evidence rather than hope.

The Heartbeats and State Machines article demonstrates these properties in a concrete liveness detector. The Statistics as State Transitions article shows how streaming algorithms achieve closure and boundedness. These are not abstract concepts—they are engineering techniques that produce systems worthy of trust.

As with any architectural approach, suitability depends on system requirements, risk classification, and regulatory context. But for systems where silent failure is unacceptable, these three properties offer a path from intuition to confidence.

About the Author

William Murray is a Regenerative Systems Architect with 30 years of UNIX infrastructure experience, specializing in deterministic computing for safety-critical systems. Based in the Scottish Highlands, he operates SpeyTech and maintains several open-source projects including C-Sentinel and c-from-scratch.

Discuss This Perspective

For technical discussions or acquisition inquiries, contact SpeyTech directly.

Get in touch
← Back to Insights