Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Atomic

Pattern

A reusable solution you can apply to your work.

Also known as: Atomic Operation, All-or-Nothing

Understand This First

  • State – atomicity matters because state can be observed between steps.
  • Database – databases provide the transaction machinery that implements atomicity.

Context

When a system modifies State, there’s always a window of time during which the change is in progress, half done. An atomic operation is one that the rest of the system can never observe in that half-done condition. It either completes fully or doesn’t happen at all. This is an architectural pattern because atomicity is a building block for Consistency and Transactions, and because its absence causes some of the most subtle and damaging bugs in software.

Problem

How do you prevent other parts of the system from seeing data in a partially updated state?

Consider transferring money between two accounts. The operation has two steps: debit one account and credit the other. If the system crashes between the two steps, or if another process reads the data between them, one account has been debited but the other hasn’t been credited. Money has vanished. The problem isn’t the crash or the concurrent read; the problem is that the two-step operation wasn’t atomic.

Forces

  • Most meaningful operations involve multiple steps, but the system should behave as if they happen instantaneously.
  • Hardware and software can fail at any point, including between steps of a multi-step operation.
  • Concurrent users and processes may read data at any moment, including during an update.
  • Making everything atomic is expensive; making nothing atomic is dangerous.

Solution

Identify operations where partial completion would leave the system in an invalid or misleading state, and ensure those operations are atomic. They either complete entirely or leave no trace.

At the database level, atomicity is provided by Transactions. Wrap related writes in a transaction, and the database guarantees that either all of them commit or none of them do. If the process crashes midway through, the database rolls back the incomplete changes automatically.

At the code level, atomicity can be achieved through language-level constructs like locks, compare-and-swap operations, or atomic data types that the CPU handles as single instructions. For example, incrementing a shared counter should use an atomic increment rather than a read-modify-write sequence, which can lose updates when two threads execute simultaneously.

At the system level, atomicity often requires careful design. Sending an email and updating a database are two different systems, and you can’t make them atomic in the traditional sense. Instead, you write to the database first and process the email from a queue. That way a failure in email delivery doesn’t corrupt the database, and the email can be retried.

How It Plays Out

A user submits a form that creates an order and decrements inventory. Without atomicity, a crash after creating the order but before decrementing inventory means the system thinks the item is still in stock, but the order exists. Wrapping both operations in a database transaction makes them atomic: either both happen or neither does.

An AI agent generating code that updates multiple related records often writes sequential statements without wrapping them in a transaction. The code works in testing, where crashes and concurrency are rare, but fails in production. Reviewing agent-generated code for multi-step state changes and wrapping them in transactions is one of the highest-value things you can do in code review.

Tip

A useful heuristic when reviewing code: any time you see two or more writes that must succeed or fail together, they should be wrapped in a transaction. If an AI agent generated the code, this wrapping is almost certainly missing.

Example Prompt

“These two database writes — creating the order and decrementing inventory — must succeed or fail together. Wrap them in a transaction so a crash between them can’t leave the data inconsistent.”

Consequences

Atomic operations eliminate an entire category of bugs: the ones caused by seeing or acting on partially updated data. They make concurrent systems safe and crash recovery straightforward. You don’t need to write cleanup logic for half-completed operations because half-completed operations can’t exist.

The cost is performance. Atomicity requires coordination (locks, transaction logs, consensus protocols), and coordination takes time. Long-running atomic operations can block other work, reducing throughput. Atomicity across system boundaries — a database and an email server, for instance — is inherently difficult and often requires compromise. The practical approach is to make operations atomic within a single system (especially a single database) and use compensating patterns like retries, queues, and idempotent receivers across system boundaries.

  • Enables: Transaction — transactions provide atomicity for groups of database operations.
  • Enables: Consistency — atomicity is a prerequisite for maintaining consistency under concurrency.
  • Uses / Depends on: State — atomicity matters because state can be observed between steps.
  • Enables: Idempotency — atomic operations that are also idempotent are safe to retry after failures.
  • Uses / Depends on: Database — databases provide the transaction machinery that implements atomicity.