Formal methods
Describe a system’s design, or its code, in a precise mathematical language, then let a tool check every possible behaviour, or prove a property for all of them, instead of trying a few by hand.
1 technology on this page ↓How it works
You write a specification: the system’s state (a counter, the log on each replica, the messages in flight), its initial states, and the steps that can change it, such as a client sending a write, a node crashing or a message arriving late. Alongside it you state the properties the design must keep. Safety properties say something bad never happens (“never two leaders in one term”, “no acknowledged write is lost”); liveness properties say something good eventually happens (“every request is eventually answered”). Then a tool checks the specification against the properties, in one of a few ways. An explicit-state model checker such as TLC enumerates every reachable state of a small, finite instance (three nodes, two values, at most four messages) and either reports that the property holds for that instance or prints a counterexample: the exact sequence of steps that breaks it. Symbolic and bounded checkers such as Apalache and the Alloy Analyzer hand the problem to an SMT or SAT solver, which copes with large data domains but looks only up to a given depth or scope. A proof system such as TLAPS or Lean proves a property for every size and every execution, but a person has to write the proof, usually around an inductive invariant. Verification-aware languages such as Dafny go one step further and check the program itself, then compile it to C#, Java, Go, Python or JavaScript. Most cloud and database teams use the lightweight end of this range: they model only the tricky part, the replication or reconfiguration protocol rather than the whole service, at the level of the design, and model-check small instances. AWS engineers described it as “exhaustively testable pseudo-code”. The catch is that the tool checks the model, not your code: a design proven correct can still be implemented wrongly, and the model is only as right as its assumptions about the network, clocks and storage. Teams narrow that gap with trace validation, replaying logs from tests or production against the specification (PObserve does this for P), and with ordinary testing, simulation and fault injection.
A chess engine analysing an endgame. Instead of playing a few games from the position and hoping, it looks at every legal sequence of moves and either shows that you can’t lose or gives you the exact line that loses. But it only knows the board you set up: put a piece on the wrong square and its verdict is about a different game.
Reach for it when
- Concurrent and distributed protocols whose bugs need a rare interleaving of messages, crashes and retries: replication, leader election, reconfiguration, distributed transactions, cache invalidation, lock-free data structures
- Before building a protocol, or before an optimisation to one already in production, when you need to know the faster version is still safe
- Pinning down guarantees other people build on, the way Azure Cosmos DB publishes TLA+ specifications of its five consistency levels
- Small, critical cores worth proving outright, such as an authorisation engine: AWS models Cedar in Lean, proves its security properties and tests the Rust code against the model
Think twice when
- Code whose risk lies in business rules, user interfaces or integrations rather than concurrency and failure; tests and types catch those bugs for far less effort
- Treating a passing model check as proof the service works: it covers the model, not the code, and only the instance sizes you configured
- Specifications nobody will keep in step with the system; Datadog notes that keeping its models current is tedious, so routine changes rely on unit, integration and chaos tests
- Modelling every field and message at once: the number of states grows exponentially until the checker never finishes, so abstract away everything the property doesn’t depend on
A real example
Two workers each add 1 to a shared counter by reading it and then writing back the value plus one. Code review passes, and the unit tests pass because they run one worker at a time. A short TLA+ specification models each worker as two steps, read and write, and states one invariant: once both workers are done, the counter is 2. TLC explores every interleaving and reports that the invariant is violated, with a four-step trace: worker 1 reads 0, worker 2 reads 0, and both write 1. The team switches the design to a compare-and-set that retries when the value has changed, models that version, and TLC finds no violation for two workers or for three. The specification says nothing about whether the Go code really retries; a test for that still has to be written.
Technologies
Specific products, and what each does differently.
TLA+ | Open source | TLA+ Foundation (Linux Foundation), created by Leslie LamportTLA+ Foundation project | MIT (TLC and the other core tools, VS Code extension); TLAPS: BSD 2-Clause; Apalache: Apache 2.0 | 2026-10-28 |
What each provider calls it
- AWS
- No service checks your designs, but AWS teams have used TLA+ since 2011 and P (open source, MIT) on S3, EBS, DynamoDB, Aurora, EC2 and others. Customers meet automated reasoning inside products: IAM Access Analyzer uses logic-based reasoning on resource policies, and Amazon Bedrock Guardrails has Automated Reasoning checks (generally available since August 2025) that test model answers against a formalised policy
- Google Cloud
- No formal methods service; Google engineers used TLA+ and the TLAPS prover to verify a concurrent union-find data structure deployed for clustering tens of billions of data points
- Azure
- No formal methods service; TLA+, its proof system and Dafny grew up at Microsoft Research, and Azure Cosmos DB publishes its TLA+ consistency specifications on GitHub under MIT
- Open source & others
- TLA+ (MIT) with the TLC and Apalache model checkers and the TLAPS prover; P (MIT) for systems of communicating state machines; Alloy 6 for relational models; Lean 4 (Apache 2.0) for proofs; Dafny (MIT) for verified code. Jepsen, by contrast, is testing: it runs the real database under injected faults and checks the recorded history
Four ways to check#
They trade coverage against effort. Most teams start with the first and add the others only where the risk justifies them.
| Approach | What you get | What it costs | Examples |
|---|---|---|---|
| Explicit-state model checking | Every reachable state of a small, finite instance, and a step-by-step counterexample when a property fails | Says nothing about larger instances; the state count explodes as you add nodes, values and messages | TLC for TLA+, the P checker |
| Symbolic or bounded checking | All behaviours up to a depth or scope, solved by SAT or SMT, including large data domains such as integers | Bounded runs say nothing beyond the bound unless you also check an inductive invariant | Apalache for TLA+, the Alloy Analyzer |
| Machine-checked proof | A property proved for every size and every execution | A person writes the proof, which takes far longer than a model check and needs a strong invariant | TLAPS, Lean, Isabelle (a TLAPS back end) |
| Verified code and trace checking | The program, not just a model, meets its specification, or real runs are checked against the model | Code written in a verifying language, or logging that maps code events onto specification steps | Dafny, Cedar’s Lean model with differential tests of its Rust code, PObserve for P |
Formal methods, testing and chaos engineering#
Testing and fault injection run the real code, so they catch implementation bugs, but only along the paths they happen to exercise. Jepsen, for example, sets up a real cluster, sends operations from several clients while a “nemesis” injects faults, records when each operation started and ended, and checks that history against the consistency the system claims. Model checking is the reverse: it explores every interleaving, but of a model someone wrote.
Teams that rely on formal methods use both. Datadog designed Courier, its multi-tenant message queue on FoundationDB, by checking a TLA+ model with TLC (5,515,710 distinct states), then running a SimPy simulation for throughput and a chaos test in staging for node loss; its engineers note that models and simulations “do not validate its implementation”. AWS’s 2025 account of its practice lists TLA+ and P beside property-based testing, fault injection and deterministic simulation.
In code · text
---------------------------- MODULE Counter ----------------------------
\* Two workers each add 1 to x by reading it, then writing back x + 1.
EXTENDS Naturals
VARIABLES x, pc, tmp
Workers == {1, 2}
Init == /\ x = 0
/\ pc = [w \in Workers |-> "read"]
/\ tmp = [w \in Workers |-> 0]
Read(w) == /\ pc[w] = "read"
/\ tmp' = [tmp EXCEPT ![w] = x]
/\ pc' = [pc EXCEPT ![w] = "write"]
/\ UNCHANGED x
Write(w) == /\ pc[w] = "write"
/\ x' = tmp[w] + 1
/\ pc' = [pc EXCEPT ![w] = "done"]
/\ UNCHANGED tmp
Next == \E w \in Workers : Read(w) \/ Write(w)
Spec == Init /\ [][Next]_<<x, pc, tmp>>
\* The property: once both workers are done, x is 2
BothCounted == (\A w \in Workers : pc[w] = "done") => x = 2
=========================================================================
\* Counter.cfg holds two lines: SPECIFICATION Spec and INVARIANT BothCounted
\* TLC reports that BothCounted is violated, with the trace
\* 1 reads 0, 2 reads 0, 1 writes 1, 2 writes 1Often confused with
Where it came from1994–2025
For decades formal methods had a reputation in industry, as the AWS engineers later put it, for “requiring a huge amount of training and effort to verify a tiny piece of relatively straightforward code”. Leslie Lamport took a different route for concurrent systems: his Temporal Logic of Actions (1994) describes a system as a set of allowed steps, and TLA+, with Yuan Yu’s TLC model checker (1999) and the book Specifying Systems (2002), made such specifications checkable by machine. Hardware teams came first: DEC/Compaq engineers verified cache-coherence protocols for Alpha processors in TLA+, and an Intel team found “hundreds of bugs before RTL” in a cache-coherence design.
Cloud services brought it into software. In 2011 Chris Newcombe at AWS, unhappy with bugs that survived reviews and tests, read Pamela Zave’s analysis of the Chord protocol in Alloy, tried Alloy, then chose the more expressive TLA+. By September 2014 AWS had used it on 10 large systems, including S3, DynamoDB and EBS; in DynamoDB the model checker found a data-loss bug whose shortest trace had 35 steps. Engineers learned it in two to three weeks, and the 2015 paper “How Amazon Web Services Uses Formal Methods” in Communications of the ACM argued that the reputation for poor return on investment was “quite wrong”.
Others followed and published: Azure Cosmos DB put TLA+ specifications of its consistency levels on GitHub in 2018, MongoDB has modelled its replication, reconfiguration and transactions since about 2015, Oracle Cloud Infrastructure since 2015, and Datadog described its Courier queue in 2024. AWS widened its toolkit to P, Dafny and Lean, as Marc Brooker and Ankush Desai described in Communications of the ACM in 2025, and the Linux Foundation launched the TLA+ Foundation in 2023 to fund the language’s tools.
Why it’s built this way
- Check the design, not the code
- AWS wanted to verify that code implements its high-level specification but knew of no tools “that can handle distributed systems as large and complex as those we are building”, so it model-checked designs, where the subtle bugs were.
- Exhaustive checks of small instances
- A model checker can’t enumerate an unbounded system, but it can cover every interleaving of a few nodes and messages, which is where AWS found bugs that had passed design reviews, code reviews and testing.
- Plain mathematics instead of a programming language
- Lamport’s view is that “the best way to describe things precisely is with simple mathematics”, which lets a specification leave out everything but the idea being checked.
- Pseudocode front ends for engineers
- AWS avoided the words “formal”, “verification” and “proof” at first, and found engineers often more productive starting in PlusCal, TLA+’s pseudocode-like language; P models systems as the communicating state machines engineers already draw.
Timeline
- 1994
Lamport publishes “The Temporal Logic of Actions” in ACM TOPLAS
- 1999
TLC, the TLA+ model checker by Yuan Yu, Panagiotis Manolios and Lamport
- 2002
Specifying Systems, the TLA+ book, is published
- 2011
AWS engineers start using TLA+
- 2015
“How Amazon Web Services Uses Formal Methods” appears in Communications of the ACM
- 2018
Azure Cosmos DB publishes TLA+ specifications of its consistency levels
- 2023
The Linux Foundation launches the TLA+ Foundation on April 21
- 2025
“Systems Correctness Practices at Amazon Web Services” describes TLA+, P and simulation at AWS
- Lamport: The Temporal Logic of Actions, ACM TOPLAS 16(3), May 1994
- Yu, Manolios and Lamport: Model Checking TLA+ Specifications, CHARME 1999
- Lamport: Specifying Systems (2002)
- Newcombe et al.: Use of Formal Methods at Amazon Web Services (September 29, 2014; Alloy and Chord, 10 systems, 35-step trace, 2–3 weeks to learn)
- Newcombe et al.: How Amazon Web Services Uses Formal Methods, Communications of the ACM 58(4), 2015
- TLA+ Foundation: Industrial Use of TLA+ (Intel, Microsoft, Azure Cosmos DB, MongoDB, Oracle, Google)
- Azure/azure-cosmos-tla on GitHub (created September 2018, MIT)
- Datadog Engineering: How we use formal modeling, lightweight simulations, and chaos testing to design reliable distributed systems (November 20, 2024)
- Brooker and Desai: Systems Correctness Practices at Amazon Web Services, Communications of the ACM, 2025
- Linux Foundation: Linux Foundation Announces Launch of TLA+ Foundation (April 21, 2023)
Sources18
- Leslie Lamport: The TLA+ Home Page (“simple mathematics”, design errors)
- Newcombe et al.: Use of Formal Methods at Amazon Web Services (2014; exhaustively testable pseudo-code, limits of verifying code)
- Apalache README (SMT-based symbolic model checking, bounded model checking, inductive invariants)
- TLA+ Proof System (TLAPS) README (Z3, Zenon, Isabelle/TLA+ back ends)
- Dafny README (verifier; compiles to C#, Go, Python, Java and JavaScript; MIT)
- P README (used across AWS on S3, EBS, DynamoDB, MemoryDB, Aurora, EC2; PObserve; MIT)
- Alloy README (Alloy 6, Kodkod model finder and SAT solvers)
- Alloy Tools home page (Alloy 6.2.0, mutable state and temporal logic)
- Lean 4 on GitHub (Apache 2.0)
- AWS Open Source Blog: Lean Into Verified Software Development (Cedar, April 8, 2024)
- Jepsen README (clients, nemesis, history checking)
- Azure/azure-cosmos-tla: TLA+ specifications for the five consistency levels of Azure Cosmos DB
- TLA+ Foundation: Industrial Use of TLA+ (Google’s union-find, MongoDB, Oracle)
- Datadog Engineering: formal modeling, lightweight simulations and chaos testing for Courier (November 20, 2024)
- Brooker and Desai: Systems Correctness Practices at Amazon Web Services, Communications of the ACM, 2025
- IAM User Guide: Using IAM Access Analyzer (logic-based reasoning on resource-based policies)
- AWS What’s New: Automated Reasoning checks is now available in Amazon Bedrock Guardrails (August 2025)
- TLA+ tools README (tla2tools.jar: SANY, TLC, PlusCal translator; Java 11+)
