skip to content

How do you design an ordering case that fails when sequence is violated, rather than one that passes because a run happened to be ordered?

level: seniorimportance: should knowfreq 46%

answer

  1. A green run is evidence about that run
  2. Cause the bad sequence deliberately
  3. Deliver the dependent event before its cause
  4. Assert the settled outcome, not the arrival log
  5. Watch the assertion actually go red once

basics

~20 s

Produce the awkward sequence deliberately instead of waiting for it: deliver the dependent event before its cause under one routing key, assert the settled outcome rather than the arrival log, and confirm the case goes red when the handling is removed.

solid answer

~50 s

A case is evidence only about the situation it created. Producing two events in the friendly sequence and finding the right result proves the friendly sequence works and nothing else, so the case must **construct the adverse sequence itself**: inject the dependent event first at a seam the run controls, or withhold its cause, or send a stale revision after a newer one — all under one ordering key. Then assert the **settled outcome**, not the sequence events appeared in, because the outcome is what a user experiences and it survives changes to how the system handles sequencing internally. Finally, prove the assertion has teeth: remove the consumer's guard in a scratch build and confirm the case fails. Repeating a friendly-sequence case many times is not a substitute — it multiplies one sample and manufactures intermittency.

code

pseudocode · 14 lines
pseudocode
key = new_unique_id()

# adverse delivery: the dependent event arrives before its cause
deliver(type: "confirmed", key: key, revision: 2)
deliver(type: "charged",  key: key, revision: 1)

wait_until account(key).settled

# assert the effect, not the sequence we just forced
assert account(key).balance == expected_balance
assert every confirmation(key) references an existing charge(key)

# teeth check, run once by hand against a build with the
# consumer's revision guard removed: this case must FAIL

go deeper

for a junior

Know that a case only proves what it actually exercised. If it never delivered the events in the awkward sequence, a green result says nothing about that sequence, however many times it has run.

for a middle

Explain how to build the adverse case: deliver the dependent event before its cause under one routing key through a seam the run controls, then assert the stored outcome rather than the sequence you just forced.

for a senior

Show the judgement being probed — designing the failure you want to catch, then proving the assertion detects it by removing the consumer's guard in a scratch build and watching the case go red before you trust its green.

for a principal

Own the standard for the codebase: an ordering claim ships with the adverse-sequence case that would falsify it, and a case nobody has ever seen fail counts as documentation rather than coverage when the team reviews what its suite actually guards.

## The run you never made proves nothing An automated case is evidence about exactly the situations it created. A case that produces a charge, produces a confirmation, waits for the stored result and finds it correct has established that the system behaves when those two events are handled in that sequence. It has established nothing about the sequence you were actually worried about, because the run never produced it. That is not weak evidence — it is evidence about a different question. This matters because the adverse sequence is rare by construction. Events get handled out of sequence when a redelivery lands late, when two producing paths race, when a consumer picks work up again after a pause, when one path is slower than another under load. A case running against a quiet deployment with one producer and one consumer takes the friendly path essentially every time. Repeating it a hundred times does not repair the logic: it multiplies one sample, and if it ever does go red you have bought an intermittent failure rather than a finding. ## Construct the sequence you are afraid of Stop hoping for the awkward interleaving and produce it on purpose. Four shapes cover most cases: 1. **Deliver the later event first.** Inject both events at the consuming boundary in reversed sequence under one ordering key. This needs a seam the run controls — a stand-in producer, a direct publish into the consumer's input — and building that seam is most of the work. 2. **Withhold the earlier one.** Deliver the dependent event, assert the system's holding behaviour, then release its cause and assert the outcome converges. 3. **Deliver a stale version.** Send an older revision of the same entity after a newer one and assert the newer one survives. 4. **Deliver both under contention.** Push the two events with no gap while a second entity's traffic runs alongside, so the consuming side is genuinely interleaving work. ## Assert the effect, not the arrival log | What the case observes | What that proves | When to use it | |---|---|---| | The sequence events appear in a log | Only that this run happened to be ordered | Rarely; it restates the delivery you forced | | The stored outcome after settling | The system converged despite the sequence | Default for a convergent design | | A guard the consumer applies (older revision ignored) | The defence exists and fires | When the design defends explicitly | | A holding behaviour and later convergence | The dependent event waited rather than corrupting | When the design defers unresolvable work | The stored outcome is almost always the better observable: it is what a user experiences, it survives a design change to how the sequence is handled internally, and it is stable under every interleaving the design permits. ## Prove that the case can fail An assertion nobody has ever watched go red is untested code with a green tick over it. Give the case teeth deliberately: - **Break the defence in a scratch build** — remove the revision guard or the deferral, run the reversed-sequence case, confirm it fails. - **Feed the assertion a violating outcome** by hand and confirm the failure text names the sequence and the key, not just "not equal". - **Invert the case's own delivery order temporarily** and confirm the result actually differs; if forward and reversed delivery produce identical passes with the defence removed, the case is not exercising what you think. - **Record what the teeth check showed** next to the case, so nobody later "simplifies" the seam away because it looks like unused indirection. That last discipline is what separates an ordering case from an ordering-flavoured smoke test. The reversed-delivery seam has a real cost, and the only justification for carrying it is that removing the behaviour it guards turns the case red. ## Two failure modes to avoid on the way **Manufacturing flakiness.** Loops, sleeps and randomised sequencing produce cases that sometimes catch the defect and always erode trust. A deterministic adverse delivery is both stricter and quieter. **Asserting on the transport instead of the product.** A case that verifies the events were delivered in the sequence the run injected is verifying the injection seam, not the consuming logic. Deliver the awkward sequence, then look only at what the product did with it. Done well, this leaves a case with a plain story: it delivers the effect before its cause under one key, it asserts the settled outcome and the invariant that no confirmation exists without its charge, and somebody has watched it fail. Green from that case is worth something. Green from a case that has only ever seen the friendly sequence is worth exactly the sequence it saw.

  • Why is running the friendly-sequence case a hundred times a poor substitute for delivering the reversed sequence?
    It turns a design question into a lottery. The adverse interleaving may need contention the run never creates, so a hundred green results say nothing; and if one goes red you have acquired an intermittent failure rather than a finding, with no way to reproduce it on demand. Constructing the sequence yourself makes both the pass and the failure deterministic.
  • How do you convince yourself the ordering assertion has teeth?
    Break the behaviour it protects and watch it fail. Remove the consumer's revision guard or its deferral in a scratch build, run the adverse case, and confirm the failure text names the key and the outcome. Record that you did it next to the case, so the seam that makes the adverse delivery possible is not later removed as unused indirection.
  • What does the adverse-delivery seam cost, and how do you justify keeping it?
    It costs a controllable injection point at the consuming boundary and the discipline to keep it out of production paths. The justification is exactly the teeth check: with the guard removed the case fails, so the seam is the only reason that defect would ever be caught. If removing the guard leaves the case green, the seam is not earning its keep and the case needs redesigning.

A smoke alarm nobody has ever held a match under is not a working smoke alarm; it is a plastic box that has never been asked a question.

saying these in an interview costs you the question

  • Believes a passing friendly-sequence run proves ordering is handled
  • Loops a case many times hoping to catch a rare interleaving
  • Never delivers the sequence the consumer is supposed to survive
  • Asserts the arrival log instead of the outcome the sequence produces
  • Never checks whether the assertion is capable of failing