Transaction answer

A transaction groups database operations so they either succeed together or fail together.

Protect the invariant, not the route

Transactions are searched when developers start handling money, inventory, credits, or multi-step writes.

Atomicity and isolation solve different failures

Use transactions when partial success would leave your data lying. The database should protect the business rule, not only store rows.

Concept Plain meaning
BEGIN Start a protected group of operations
COMMIT Save all changes
ROLLBACK Undo changes if something fails
Isolation Controls what concurrent transactions can see

Reserve a withdrawal safely

BEGIN;

UPDATE accounts SET balance = balance - 100 WHERE id = 'from_user';
UPDATE accounts SET balance = balance + 100 WHERE id = 'to_user';
INSERT INTO ledger_entries (amount, reason) VALUES (100, 'transfer');

COMMIT;

This example is intentionally small. In a real codebase, the surrounding details matter: naming, error handling, tests, runtime config, permissions, and how easy the next developer can understand the change.

Choose the smallest transaction boundary

  • Use transactions around related writes.
  • Keep transactions short.
  • Handle rollback paths.
  • Know the isolation level for critical flows.
  • Add constraints so bad data cannot sneak in.

Transaction mistakes that survive testing

  • Doing multi-step money changes without a transaction.
  • Calling slow external APIs inside a transaction.
  • Assuming ORM save calls are automatically safe.
  • Ignoring database constraints.
  • Testing only single-user happy paths.

Describe the invariant in review

Use a concrete sentence:

I used this pattern because [problem]. The main tradeoff was [tradeoff]. I verified it by [test or check].

That structure works because it shows judgment. Anyone can name a tool. Strong developers explain why they chose it, what could go wrong, and how they checked the result.

PostgreSQL references

Commit only a complete state

A transaction groups database operations so they either succeed together or fail together. Keep the implementation small, verify the edge cases, and write the decision down so the next person can trust it.

Model the race, not only the happy path

Suppose an account has 1,000 available units and two requests each try to withdraw 700. If both handlers read the balance before either writes, both can decide the withdrawal is allowed. A transaction is useful only when the isolation strategy or row lock prevents that stale decision.

BEGIN;

SELECT available_balance
FROM accounts
WHERE id = $1
FOR UPDATE;

UPDATE accounts
SET available_balance = available_balance - $2,
    reserved_balance = reserved_balance + $2
WHERE id = $1
  AND available_balance >= $2;

INSERT INTO withdrawals (id, account_id, amount, status)
VALUES ($3, $1, $2, 'pending');

COMMIT;

The application must check that the conditional update affected one row. In CryptoEx, this boundary is where a withdrawal record and balance reservation belong together. The external transfer does not belong inside a long database transaction because a provider call can be slow or uncertain. A durable worker continues that part after commit.

Test rollback and duplicate delivery

Force the insert to fail after the balance update and verify the balance remains unchanged. Then submit the same logical request twice and verify a unique idempotency key prevents two pending withdrawals. These tests expose partial-state and retry bugs that a single successful request cannot reveal.

Keep transactions short, avoid user interaction or network calls inside them, and record enough context to reconcile an uncertain operation later.

Questions that define the boundary

Which rows must change together? What business state would be impossible if only half the statements commit? Can two concurrent requests both pass the same check? Does the code perform a network call while locks are held? What stable identifier prevents the workflow from being accepted twice?

Answering these questions usually produces a smaller transaction and a clearer asynchronous boundary. The database protects durable state; a worker handles slow external work and reconciliation after commit.