Lesson 67: Sharing State Between BDD Steps
The Junior Trap: The Global Variable Time-Bomb
Imagine you’re writing a Gherkin scenario for an order creation flow:
Scenario: Customer places an order
When the customer submits a new order
Then the order confirmation email is sent to the order ID
The “When” step creates an order, gets back an
order_idfrom the API. The “Then” step needs that exact sameorder_idto verify the email. How do you pass it across?A junior dev’s first instinct looks like this:
# steps/order_steps.py ← THE TRAP
_order_id = None # module-level global
@when("the customer submits a new order")
def submit_order():
global _order_id
response = api.post("/orders")
_order_id = response.json()["order_id"]
@then("the order confirmation email is sent to the order ID")
def verify_email():
assert email_service.was_sent_to(_order_id) # reads global
On your laptop, with one test running at a time, this works perfectly. You show it in a demo. Everyone claps.
Then you push to CI.
The Failure Mode: Silent Data Corruption in Parallel
Your CI/CD pipeline uses
pytest-xdistto run tests across 4 workers in parallel — because waiting 20 minutes for a serial suite is not an option at scale.Here’s what happens:
Worker 1: submits Order A → _order_id = "ORD-001"
Worker 2: submits Order B → _order_id = "ORD-002" ← overwrites Worker 1's value
Worker 1: verifies email → reads _order_id = "ORD-002" ← WRONG ID
The test for Order A now verifies the email for Order B. If that email also happens to exist (maybe it does, maybe the test is slow and it doesn’t yet), you get a non-deterministic result. Sometimes green. Sometimes red. The worst kind of failure — the one that has no consistent reproduction path.
This class of bug is called a shared mutable state race condition. It is the #1 cause of flaky test suites in production pipelines.
Preparing for a distributed systems interview?
→Download the free Interview Pack
→ Subscribe now to access source code repository - 200 + coding lessons



