Programming Fundamentals › Concurrency & Async
Check-Then-Act Race (TOCTOU)
Checking a condition and acting on it as two steps, so something changes in between.
Also known as: TOCTOU, time of check to time of use, check-then-act
A check-then-act race happens when code checks a condition, then acts on it, but another thread or process changes the condition between the two steps. The check was true when it ran, and the action runs on a world that no longer matches it. This is also called a time-of-check to time-of-use (TOCTOU) bug.
A bank balance shows it clearly:
import threading, time
balance = 100
def withdraw(amount):
global balance
if balance >= amount: # check
time.sleep(0) # another thread can run here
balance -= amount # act, on a balance that may have changed
threads = [threading.Thread(target=withdraw, args=(60,)) for _ in range(2)]
for t in threads: t.start()
for t in threads: t.join()
print(balance) # e.g. -20: both checks passed before either withdrawal
Both threads see 100, both pass the check, and the account goes negative. The fix is to make the check and the act one unit, usually with a lock around both:
lock = threading.Lock()
def withdraw_safe(amount):
global balance
with lock:
if balance >= amount:
balance -= amount
The trade-off is that holding a lock across the check and the act serializes that code. For a short update, that’s cheap. If the critical block includes slow I/O, you’ve turned a rare bug into a bottleneck. Keep the locked section small.
The same bug appears outside memory, such as checking whether a file exists and then creating it, or checking a seat is free and then booking it in a database. In those cases, use an atomic operation provided by the system, such as an exclusive create or a conditional update, rather than a separate check. Critical sections explain how to choose the code to lock.