Spent some time fighting a race condition in Bubble.io. Here’s what was happening, why it’s sneaky, and how I fixed it, for anyone else who runs into this.
The setup:
A backend workflow scheduled on a list (Schedule API Workflow on a list). Each instance does two things:
- Creates a new Thing (let’s call it an Assessment Item) — works perfectly every time.
- Adds that newly created item to a list field on a parent Thing (call it Assessment) using “Add.”
The symptom:
Step 1 never failed. Every item got created correctly. But step 2 was wildly inconsistent — sometimes the list had only one entry, sometimes two, never the full set I expected. No errors, no warnings. Just silent data loss.
What’s actually happening:
Scheduled workflows on a list don’t run sequentially — they run in parallel, near-simultaneously. Each workflow instance independently:
- Reads the current state of the parent Assessment thing
- Adds its new item to that list, in memory
- Saves the whole list back
The problem is that “read” happens before the other instances have saved their writes. So instance A reads the list with 0 items, adds its item, and saves a list of 1. Instance B does the exact same thing, also starting from 0, and saves its own list of 1. Whichever instance saves last “wins” and overwrites the others. It’s a classic read-modify-write race condition — and Bubble’s “Add” doesn’t atomically append on the database level, it works on the data that was already loaded into that specific workflow run.
This is why it looked random: it depended purely on timing, not logic.
The fix:
Force the writes to happen sequentially instead of in parallel. A few ways to do this in Bubble:
- Use “Schedule API Workflow on a list” with a slight incremental delay per item (even staggering by a second or two per index) so each instance’s read happens after the previous instance’s write completes - (This may not be enough as I have noticed)
- Or switch to a recursive workflow pattern — process one item, then trigger the next item’s workflow only after the first one finishes, instead of firing them all at once
- Or, if scale allows, do step 2 in a single workflow run after all step 1 items are created, looping through and building the full list once, instead of having each parallel instance modify the same parent record
I went with the recursive pattern since the dataset wasn’t huge, and it instantly became 100% consistent.
If you’ve never hit this, it’s worth knowing: any time multiple Bubble workflows touch the same parent record’s list field in parallel, you’re exposed to this. It’s not a Bubble bug, it’s a fundamental concurrency issue, Bubble just doesn’t make it obvious until your data starts mysteriously disappearing.
Hope this saves someone from loosing their time. ![]()
Would love to hear your thoughts if someone has faced something similar.
