Partial Order Reduction

September 18, 2026 · Concurrent and Distributed Systems

Contents
  1. 1. Why verify concurrent programs?
  2. 2. A running concurrent-system example
  3. 3. Model checking the global state space
  4. 4. Partial order reduction
    1. 4.1 Independent transitions
    2. 4.2 Mazurkiewicz trace equivalence
    3. 4.3 Persistent sets and sleep sets
  5. 5. Full and reduced search spaces
Colored hand-drawn animated graphical abstract showing the journey from concurrent systems and interleavings through model checking and partial-order reduction to verified properties

1. Why Verify Concurrent Programs?

A concurrent program consists of multiple processes or threads whose actions may execute concurrently and interact through shared variables, communication, synchronisation, or other shared resources. Under the interleaving model of concurrency, a single concurrent execution may be represented by different orderings, or interleavings, of its concurrent events. Thus, even for a fixed input, different scheduling choices can lead to different execution paths and potentially different reachable states. This proliferation of possible interleavings is a fundamental source of complexity in the verification of concurrent systems.[1, Ch. 2]

Verification goal: We want to establish that the required property holds for every relevant execution, not merely for the one schedule observed during testing.

Consider the two processes shown below, both operating on the shared variables x and y. In the first interleaving, their operations alternate and the execution terminates with y = 2, satisfying the assertion. In the second, one process executes before the other, resulting in y = 1 and an assertion failure. Hence, checking a single execution is not sufficient: correctness must account for the relevant interleavings that the concurrent program may exhibit.

Two processes running identical code x=0; x++; y=x; under a lockstep interleaving (x=2, y=2, assert(y==2) passes) versus a sequential interleaving (x=1, y=1, assert(y==2) fails)

2. A Running Concurrent-System Example

I will use the following concurrent program throughout to explain the main concepts. It consists of two processes operating on the shared Boolean variables x and y, which are initially zero.

Process A

x = 0;// t1Ay = 0;   // t2A
a0x=0a1y=0a2

Process B

y = 1;// t1Bx = 1;   // t2B
b0y=1b1x=1b2

The statements inside each process execute in program order, as shown by the arrows between its local control states. The scheduler may interleave transitions from Process A and Process B.

I use the formal model of Labelled Formal Concurrent System (LFCS) semantics to represent the above example. The local states tell us where each process is; the shared-object valuation tells us what the processes have collectively written so far.[1, §§2.1–2.2]

Labelled Formal Concurrent System Semantics

  • Processes: P = {A, B}, where A = {a0,a1,a2} and B = {b0,b1,b2}.
  • Shared objects: O = {x, y}, with value domains Vx = Vy = {0,1}.
  • Global state: s = (ai,bj,x,y) ∈ A × B × Vx × Vy.
  • Initial state: s0 = (a0,b0,0,0).
TransitionLFCS transitionMeaning
t1A(a0, true, x := 0, a1)Process A writes x.
t2A(a1, true, y := 0, a2)Process A writes y.
t1B(b0, true, y := 1, b1)Process B writes y.
t2B(b1, true, x := 1, b2)Process B writes x.

The two program-order constraints are t1A < t2A and t1B < t2B. Subject to those constraints, there are six complete schedules, which we will see in the next section. They start from the initial state (a0,b0,0,0) and terminate in three possible states: (a2,b2,1,1), (a2,b2,0,0), and (a2,b2,1,0).

3. Model Checking the Global State Space

Model checking is an automated verification technique that systematically explores the behaviours of a program or system and checks whether each reachable execution satisfies a desired property. In stateless model checking, the verifier repeatedly executes the program under different schedules without storing every previously reached global state; this keeps memory usage low, but may revisit the same states and execution prefixes many times. In stateful model checking, the verifier constructs the reachable-state graph and stores visited states, avoiding repeated exploration at the cost of potentially substantial memory consumption.[1, §2.2]

Stateful depth-first search

A standard stateful model checker explores the reachable-state graph using the following depth-first search algorithm. The stack stores states that remain to be explored, while the visited set H ensures that each reachable state is expanded at most once.

Algorithm 1: Stateful DFS [1, Fig. 2.1]
  1. Initialize: Stack is empty; H is empty;
  2. push(s0) onto Stack;
  3. while Stack ≠ ∅ do {
  4. s := pop(Stack);
  5. if s ∉ H then {
  6. insert s into H;
  7. T := enabled(s);
  8. for each t ∈ T do {
  9. s′ := successor of s after executing t;
  10. push(s′) onto Stack;
  11. }
  12. }
  13. }

The graph constructed by this algorithm is the global state space AG, which contains every state reachable from s0. Each node records a snapshot of both processes' control locations and the shared values, while each edge represents one enabled process transition. Whenever DFS removes a state s from the stack for the first time, it records s in H, computes the transitions enabled there, and pushes every successor onto the stack. Repeating this process until the stack is empty constructs the complete reachable-state graph shown below.[1, §2.2]

Global state graph of the two-process running example Thirteen states and fourteen transitions connect the initial state s0 to final states s10, s11, and s12. t1A t1B t2A t1B t1A t2B t1B t2A t2B t1A t2B t2B t2A t2A s0(a0,b0,0,0) s1(a1,b0,0,0) s2(a0,b1,0,1) s3(a2,b0,0,0) s4(a1,b1,0,1) s5(a0,b2,1,1) s6(a2,b1,0,1) s7(a2,b1,0,0) s8(a1,b2,1,1) s9(a1,b2,0,1) s10(a2,b2,1,1) s11(a2,b2,1,0) s12(a2,b2,0,0)
t1A: x:=0t2A: y:=0t1B: y:=1t2B: x:=1
Figure 2: The complete global state space has 13 states, 14 transitions, and six paths from the initial state to a terminal state.

Example property: deadlock freedom

A reachable state is a deadlock when no transition is enabled there.[1, Def. 3.11] In the exploration graph, the only states without outgoing edges are the three final states s10 = (a2,b2,1,1), s11 = (a2,b2,1,0), and s12 = (a2,b2,0,0). Here, these states represent normal termination because both processes have reached their final local states, a2 and b2. In a system expected to continue running, however, reaching a non-final state with no enabled transition would constitute a deadlock. Thus, the checker determines deadlock freedom by inspecting every reachable state with no outgoing edge and deciding whether it is a valid terminal state.

The state-space explosion problem

For a concurrent program, a global state combines the local state of every process with the values of all shared objects. As the number of processes and enabled actions increases, the product of these local possibilities grows rapidly. Moreover, independent transitions may be scheduled in many different orders, so the checker can encounter a combinatorial number of paths even when those paths differ only in the ordering of unrelated operations. Consequently, both the visited set and the transition graph may grow exponentially, exhausting time or memory long before the search completes.[1, Ch. 3]

4. Partial Order Reduction

Partial Order Reduction (POR) is a model-checking technique for reducing the number of interleavings that must be explored. It identifies schedules that differ only in the ordering of independent transitions and explores representative transitions instead, while preserving the behaviours relevant to the property being checked.[1, Ch. 3]

Two sequences of transitions are equivalent if they can be obtained from each other by swapping adjacent, independent transitions. Thus, given a valid dependency relation, sequences of transitions can be grouped into equivalence classes, which Mazurkiewicz calls traces.[1, §3.2] In practice, this reduction is realized using persistent sets, which restrict the transitions explored from a state, and sleep sets, which prevent equivalent transition orderings from being explored repeatedly.[1, Chs. 4–5]

4.1 Independent Transitions

Definition: Dependency Relation and Independent Transitions [1, Def. 3.1]

Let T be the set of transitions in LFCS and D ⊆ T × T be a binary, reflexive, and symmetric relation. The relation D is a valid dependency relation for the LFCS iff for all t1, t2 ∈ T, (t1,t2) ∉ D (t1 and t2 are independent) implies that the following two properties hold for all global states s ∈ S of the LFCS:

  1. If t1 is enabled in s and s —t1→ s′, then t2 is enabled in s iff t2 is enabled in s′ (independent transitions can neither enable nor disable each other); and
  2. If t1 and t2 are enabled in s, then there is a unique state s′ such that s —t1t2→ s′ and s —t2t1→ s′ (independent transitions are commutative).

Example

t1A writes x, whereas t1B writes y. At s0, they are simultaneously enabled, neither transition changes the enabledness of the other, and both execution orders reach s4 = (a1,b1,0,1). Therefore, they are independent. The same reasoning applies to t2A and t2B.

By contrast, t1A and t2B both write x, while t2A and t1B both write y. Reordering either pair can change the final valuation, so these pairs are dependent. Transitions belonging to the same process are also dependent because their program order must be preserved.

PairClassificationReason
t1A, t1BIndependentThey write different variables and commute.
t2A, t2BIndependentThey write different variables and commute.
t1A, t2BDependentBoth write x; swapping them changes the final value.
t2A, t1BDependentBoth write y; swapping them changes the final value.
t1A, t2A or t1B, t2BDependentProgram order within each process must be preserved.

4.2 Mazurkiewicz Trace Equivalence

With independence now defined, we can apply Mazurkiewicz trace equivalence to the running example. Two schedules belong to the same trace when one can be transformed into the other by repeatedly swapping adjacent independent transitions. The equivalence class of a schedule w, written [w], captures the essential causal order while ignoring accidental scheduling order between independent transitions.[1, Defs. 3.4–3.5]

Respecting t1A < t2A and t1B < t2B gives exactly six complete schedules. Schedules with the same color belong to the same trace-equivalence class:

t1A · t2A · t1B · t2B(x,y) = (1,1)
t1B · t2B · t1A · t2A(x,y) = (0,0)
t1A · t1B · t2A · t2B(x,y) = (1,0)
t1A · t1B · t2B · t2A(x,y) = (1,0)
t1B · t1A · t2B · t2A(x,y) = (1,0)
t1B · t1A · t2A · t2B(x,y) = (1,0)

For example, t1A·t1B·t2A·t2B becomes t1B·t1A·t2A·t2B by swapping the adjacent independent pair t1A,t1B. It also becomes t1A·t1B·t2B·t2A by swapping t2A,t2B. Repeating these legal swaps connects all four green schedules in the same trace-equivalence class.

Reduction target: instead of exploring six schedules, a verifier needs one suitable representative from each of the three trace classes—provided the reduced search preserves the property under consideration.

4.3 Persistent Sets and Sleep Sets

A persistent set P(s) selects a sufficient subset of the transitions enabled in state s. The intuition is that transitions omitted from P(s) can be postponed while the selected transitions are explored, because they remain independent of those selected transitions. The checker can therefore branch only on P(s) without losing a distinct trace that is relevant to verification.[1, §4.1]

Definition: Persistent Set [1, Def. 4.1]

A subset P of the set of transitions enabled in a state s of AG is called persistent in s if all transitions not in P that are enabled in s, or in a state reachable from s through transitions not in P, are independent of all transitions in P.

Applying this rule to the running example gives the persistent-set exploration below. The orange annotation at each expanded state shows the selected set P(s). The two paths highlighted in green are equivalent executions belonging to the same trace class, yet persistent-set exploration still visits both. Persistent sets alone are therefore not sufficient for eliminating every redundant interleaving; removing this requires sleep sets.

Persistent-set based exploration A reduced graph with twelve states, twelve transitions, four complete interleavings, and the persistent set selected at each expanded state. t1A t1B t2A t1B t1A t2B t1B t2A t1A t2B t2B t2A s0P={t1A,t1B}(a0,b0,0,0) s1P={t2A,t1B}(a1,b0,0,0) s2P={t1A,t2B}(a0,b1,0,1) s3P={t1B}(a2,b0,0,0) s4P={t2A}(a1,b1,0,1) s5P={t1A}(a0,b2,1,1) s6P={t2B}(a2,b1,0,1) s7P={t2B}(a2,b1,0,0) s9P={t2A}(a1,b2,0,1) s10(a2,b2,1,1) s11(a2,b2,1,0) s12(a2,b2,0,0)
Figure 3: Persistent sets reduce six interleavings to four.

A sleep set Sleep(s) is used together with persistent sets to remove duplicate representatives that persistent-set selection alone can still generate. It records enabled transitions that should not be executed from the current search node because an equivalent ordering of independent transitions has already been explored.[1, §5.1] Persistent sets decide which transitions are sufficient to explore, while sleep sets remember which of those choices would only reproduce an already represented trace. In the graph below, t1A is asleep at s2, so the redundant edge from s2 to s4 is not explored.

Persistent-set and sleep-set based exploration A reduced graph with twelve states, eleven transitions, and three complete interleavings. The edge from s2 to s4 is pruned because transition t1A is sleeping at s2. t1A t1B t2A t1B t2B t1B t2A t1A t2B t2B t2A s0P={t1A,t1B}Sleep=∅ s1P={t2A,t1B}(a1,b0,0,0) s2Sleep={t1A}(a0,b1,0,1) s3P={t1B}(a2,b0,0,0) s4P={t2A}(a1,b1,0,1) s5P={t1A}Sleep=∅ s6P={t2B}(a2,b1,0,1) s7P={t2B}(a2,b1,0,0) s9P={t2A}(a1,b2,0,1) s10(a2,b2,1,1) s11(a2,b2,1,0) s12(a2,b2,0,0)
Figure 4: Sleep sets remove the remaining redundant interleaving, leaving three trace representatives.

5. Full State Space vs. Reduced Search Space

Classical search13 states
14 transitions
6 interleavings
→
Persistent sets12 states
12 transitions
4 interleavings
→
Persistent + sleep12 states
11 transitions
3 interleavings

The small example saves only a few nodes and edges, but the mechanism scales. With three or more processes, the unreduced graph contains combinations of many independent local steps. POR keeps the causally different behaviours while discarding the factorial number of ways unrelated steps can be interleaved.

Takeaway

Partial Order Reduction replaces the question “How do I enumerate every schedule?” with “Which schedules are genuinely different under the program's dependency relation?” The running example makes the progression concrete: four transitions generate six schedules, independence groups those schedules into three traces, persistent sets reduce the available branches, and sleep sets eliminate the remaining duplicate representative.

ConceptRole in POR
Global state spaceRepresents every reachable combination of local process states and shared-object values.[1, §2.2]
IndependenceDefines when two adjacent transitions may be swapped without changing enabledness or result.[1, Def. 3.1]
Mazurkiewicz traceGroups total-order executions that differ only by swaps of adjacent independent transitions.[1, §3.2]
Persistent setRestricts which enabled transitions are explored from a state while preserving the needed behaviors.[1, Def. 4.1]
Sleep setSuppresses an ordering when its trace class has already been represented.[1, §5.1]

References and source material

  1. Patrice Godefroid, Partial-Order Methods for the Verification of Concurrent Systems: An Approach to the State-Explosion Problem, Lecture Notes in Computer Science 1032, Springer, 1996. Cited sections: §§2.1–2.2, 3.1–3.3, 4.1, and 5.1.

Get In Touch