Imagine two users trying to update the same record at the exact same time. If your app isn’t handling this properly, one update might o...
Imagine two users trying to update the same record at the exact same time. If your app isn’t handling this properly, one update might overwrite the other, leading to incorrect results. This issue is called a race condition, and it can cause unexpected bugs.
Consider an online payment system where a user’s account balance is updated. If two transactions occur at nearly the same time, they might read the same balance before either updates it—resulting in incorrect deductions.
Is your app facing race conditions? It’s time to fix that. In this article, we’ll look at the benefits and four ways to prevent race conditions. This will help keep data safe and performance smooth.
Ensure multiple operations succeed or fail together. If anything breaks, the transaction rolls back, preserving data consistency.
Example:
A bank transfer where money is deducted from one account but fails to reach the other.
Solution:
DB::transaction(function () {
$sender = User::find(1);
$receiver = User::find(2);
$sender->balance -= 100;
$receiver->balance += 100;
$sender->save();
$receiver->save();
});
✅ If any step fails, Laravel rolls back all changes.
Locks a record for the current operation, blocking others until it finishes. Great for high-traffic scenarios.
Example:
Two customers try to buy the last item in stock.
Solution:
$product = Product::where('id', 1)->lockForUpdate()->first();
if ($product->stock > 0) {
$product->stock -= 1;
$product->save();
} else {
throw new Exception("Out of stock!");
}
✅ Ensures only one transaction can update the stock at a time.
Checks if a record changed before saving updates. Useful when many users may modify the same data.
Example:
Two employees update a product’s stock simultaneously.
Solution:
$product = Product::find(1);
if ($product->updated_at->timestamp === request('updated_at')) {
$product->stock += request()->added_stock;
$product->updated_at = now();
$product->save();
} else {
throw new Exception("Stock data changed. Please refresh and try again.");
}
✅ Prevents updates if the data has changed since it was read.
Let the database handle updates in a single step—no fetch or manual save needed.
Example:
Multiple users try to withdraw money from the same account.
Solution:
User::where('id', 1)->decrement('balance', 100);
✅ The operation is handled directly in the database—no race condition.
Use Laravel’s Cache::lock()
to prevent multiple processes from running the same critical logic block.
Example:
Prevent multiple jobs from processing the same order at the same time.
Solution:
$lock = Cache::lock('process-order-123', 10); // Lock for 10 seconds
if ($lock->get()) {
try {
$order = Order::find(123);
if ($order && $order->status === 'pending') {
// Execute payment or fulfillment logic
$order->status = 'processing';
$order->save();
dispatch(new ChargeCustomer($order));
}
} finally {
$lock->release();
}
} else {
// Another process is handling it
Log::info('Order 123 is already being processed.');
}
✅ Prevents race conditions across processes by using cache-based locking.
Don't leave data consistency to chance —build with confidence! 🚀
Share your thoughts or tips below.
🚀 Happy coding!
Subscribe to our "Article Digest". We'll send you a list of the new articles, every week, month or quarter - your choice.
What do you think about this?
Wondering what our community has been up to?