Bug class · check-then-act races

How to spot check-then-act races (TOCTOU) in code review

A check-then-act race reads a value, decides, and then writes, with nothing stopping another request from changing the value in between. Two people get the last copy of a book, two signups claim the same username. Every test passes, because tests send one request at a time.

What it looks like in a diff

Two steps against shared state with application code between them: a read of a count or a status, an if, then a write. The check and the write are separate statements, not one atomic operation, and an await or a network call between them widens the gap.

Ask what happens when two requests run the same lines at the same time. If both can pass the check before either writes, the rule is not enforced. The fixes live in the database: a conditional update that checks the rule in its WHERE clause, a unique constraint, or a row lock held from the read to the write. A transaction alone does not help at the usual isolation levels (PostgreSQL's default READ COMMITTED, for one): both transactions can still read the old value.

Questions to ask in review

How to word the comment

Name the input, the path it takes and what goes wrong, then the fix. For example (on an imagined PR, not one from the library):

Two requests can both see one copy available and both create a hold, so the library lends a book it does not have. Make it one step: a conditional update that decrements available only WHERE available > 0, and treat zero updated rows as no copy left.

Practise on real PRs

As of October 2026, the DiffDojo library has 5 live PRs that plant check-then-act races, across 4 stacks. They are not listed here: a PR you open knowing its bug teaches less. Each review is graded on whether you find the bug and explain it.

Open the library

All bug classes · Review guides by stack · Glossary