State transition testing verifies systems where the same action produces a different result depending on the current state. Login flows, approval workflows, and payment statuses all qualify: the order of operations changes the outcome.
What actually pays off in practice is not confirming the valid transitions. It is the blank cells that appear when you build the state transition table — the operations the specification never defined. The most painful production defects usually come from there.
This article covers how to build the diagram and the table, how to choose between 0-switch, 1-switch, and N-switch coverage, and how to handle invalid transitions, with concrete examples.
Sponsored
What is state transition testing?
It verifies the relationship between the states a system holds and the events that switch between them.
An elevator has states such as “stopped”, “ascending”, and “descending”, and button presses move it between them. The goal is to confirm those movements match the specification.
The key point is that a system that looks correct can still break when operated in a particular order. It helps to think of this technique as targeting the system’s flow rather than its static data.
How it differs from other techniques
| Technique | What it looks at | Difference from state transition testing |
|---|---|---|
| Equivalence partitioning and boundary value analysis | The range of a single input field | Does not handle order |
| Decision table testing | Combinations of conditions and their results | Can only express the current state |
| Pairwise testing | Combinations of configuration values | Does not handle order |
| State transition testing | The relationship between states and events | Handles cases where order changes the result |
It is easy to confuse this with a decision table. A decision table defines “if the conditions are these, the result is that”, but it cannot express the path taken to get there. A defect like “an approved request can be pushed back to in-progress” will never surface in a decision table.
Building the state transition diagram
Three steps:
- Decide the initial state (for example, “off”)
- Enumerate the events (switch press, timer expiry, and so on)
- Connect states and events with arrows
Here is a household light as an example.
switch ON dimmer button
[OFF] ──────────────→ [ON] ──────────────→ [DIMMER MODE]
↑ │ │
│ switch OFF │ │
└────────────────────┘ │
│ │
│ timer expiry │
└─────────────────────────────────────────────┘
The point of the diagram is to see the whole picture at a glance. Past roughly five states it becomes hard to read, and the table below should take over as the primary artifact.
Sponsored
The state transition table is where the work happens
A diagram cannot show you the arrows nobody drew. A table exposes the gaps structurally.
The construction is simple: put the current states down the side, the events across the top, and fill in every cell.
| Current state \ Event | Switch ON | Switch OFF | Dimmer button | Timer expiry |
|---|---|---|---|---|
| Off | On | ? | ? | ? |
| On | ? | Off | Dimmer mode | Off |
| Dimmer mode | ? | ? | ? | Off |
Every “?” is an operation the specification does not describe. Out of 3 states x 4 events = 12 cells, only 5 are filled in.
That immediately gives you questions for the developers:
- What happens if switch OFF is pressed while already off — nothing, or an error?
- What happens if switch ON is pressed while already on — ignored, or a brightness change?
- What happens if the dimmer button is pressed again in dimmer mode — back to on, or cycling?
Most of the value of state transition testing lives in the act of building this table. It pays off during specification review, well before any test is executed. I covered how to apply this upstream in QA in waterfall development.
Coverage criteria: 0-switch, 1-switch, N-switch
These decide how far you test. Without settling this first, case counts vary wildly between engineers.
| Criterion | What it checks | Case count |
|---|---|---|
| 0-switch | Every single transition (each arrow once) | One per arrow |
| 1-switch | Every pair of consecutive transitions | Grows sharply |
| N-switch | Every sequence of N+1 consecutive transitions | Grows exponentially |
Watch the terminology. “0-switch” does not mean “visit each state once” — it means traverse every single transition. The switch count refers to the number of joins between consecutive transitions.
Concrete example
0-switch: OFF --(ON)--> ON <- each arrow once
1-switch: OFF --(ON)--> ON --(dimmer)--> DIMMER <- two arrows chained
Some defects only appear at 1-switch: behavior that is correct in isolation but changes depending on the preceding state. Implementations that carry an internal flag are the usual culprit.
Choosing in practice
0-switch as the baseline, 1-switch only on high-risk paths is the realistic answer.
- 0-switch: traverse every arrow. This is mandatory
- 1-switch: restrict to payment, permission changes, data deletion — paths where breakage hurts
- N-switch (N>=2): only where extremely high reliability is required, such as embedded devices
Applying 1-switch to every path makes the case count unrealistic. I have nearly blown an estimate chasing full coverage. Decide where to go deep before you start.
Sponsored
Invalid transitions are the ones worth testing
The operations that "cannot happen" are exactly what happens in production.
The browser back button, double clicks, two tabs open at once, recovery after a dropped connection — all of them are routes by which a user triggers an invalid transition without meaning to.
Patterns to check
| Pattern | Example |
|---|---|
| Repeating a completed action | Reopening an approved request with the back button and approving it again |
| Double execution | Hammering the submit button |
| Concurrent operations | Operating on the same request from two tabs |
| Interruption mid-flow | The connection drops during processing. In what state is the data left? |
| Permission changes | The approver's account is deleted while a request is awaiting approval |
The expected result is not "it errors" but "how it errors". Does a message appear on screen, is the action ignored, does it return to the previous screen? Leave this vague and each implementer will pick something different.
How to write it in the table
Invalid transitions are filled in explicitly, never left blank.
| Current state \ Event | Approve | Send back |
|---|---|---|
| In review | Approved | Draft |
| Approved | No change + show "already approved" | No change + action unavailable |
| Draft | No change + button hidden | No change + button hidden |
Do not settle for a dash or an empty cell. Blank is synonymous with undecided.
Do not forget events people do not trigger
User actions are not the only thing that changes state. Miss this and you lose the defects that surface only after time passes.
- Timers and batch jobs: session expiry, automatic cancellation, nightly batches
- Notifications from external systems: payment webhooks, inventory sync
- Sensor input: state changes from IoT devices
- Other users' actions: another administrator editing the same record while you have it open
"The user completed payment immediately after the reservation was auto-cancelled" — races like this cannot be reproduced unless the test design accounts for them.
Systems this suits
| Target | Example states |
|---|---|
| Request and approval flows | Draft / In review / Approved / Sent back |
| Orders and payments | Cart / Awaiting payment / Paid / Shipped / Cancelled |
| Authentication | Logged out / Logged in / Locked / Session expired |
| Media playback | Stopped / Playing / Paused / Buffering |
| IoT devices | Awaiting connection / Connected / Sleeping / Error |
Conversely, it does not suit stateless features such as search or filtering. Those belong to equivalence partitioning and decision tables.
Relationship with E2E testing
The paths you identify here become direct candidates for E2E tests. That does not mean automating all of them.
- Main 0-switch paths: worth automating in E2E
- Invalid transitions: more stable to verify in unit or API tests
- Full 1-switch coverage: maintenance cost rarely justifies automation
I wrote up how to decide what to automate in what to protect with E2E tests.
Summary
- State transition testing handles systems where order changes the result. A decision table can only express the current state
- Most of the value is in building the table. A blank cell is a hole in the specification
- "0-switch" means traversing every transition, not visiting every state once
- 0-switch as the baseline, 1-switch only on high-impact paths. Full 1-switch coverage collapses
- Fill in invalid transitions explicitly, down to how the error appears
- Back button, double clicks, and multiple tabs are how invalid transitions actually occur
- Do not forget timers, webhooks, and other events no person triggers
- Of the paths you find, automate only the main ones in E2E
Because the technique follows the system's behavior rather than its screens, it exposes gaps that reading a screen specification will never reveal. Start by building one table.