{"post":{"slug":"deferrable-constraints-explained-2026","title":"Deferrable Constraints: Checking at Commit Instead of Immediately","excerpt":"Swapping two unique values fails because the intermediate state violates the constraint. Deferring the check to commit time lets the transaction pass through invalid states legally.","category":"code","date":"January 2, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">Y</span>ou need to swap the display order of two items — one is position 1 and the other position 2, and they should trade places. There is a unique constraint on position.\n\nUpdate the first to 2 and the constraint fires immediately: two rows now have position 2. It does not matter that the next statement fixes it. The check ran per statement, and the intermediate state was invalid.\n\n## What changed in 2026\n\n- **Awareness improved without adoption growing much.** The mechanism stayed well-supported and relatively rarely used.\n- **ORM support remained inconsistent.** Framework-level control over constraint timing continued to be uneven.\n- **The debugging cost got documented.** Deferred errors reported at commit, far from the causing statement, became a recognised drawback.\n- **Alternative patterns spread.** Sequence-gap approaches and temporary sentinel values remained common workarounds.\n\n## Three timing modes\n\n| Mode | Checked | Use |\n| --- | --- | --- |\n| Not deferrable | After each statement | Default; best error locality |\n| Deferrable initially immediate | After each statement, unless deferred | Flexible; opt in per transaction |\n| Deferrable initially deferred | At commit | When intermediate violations are routine |\n\nThe middle mode is the useful default when you need deferral at all. The constraint behaves normally — errors arrive at the statement that caused them — and a transaction that needs to pass through an invalid state can explicitly request deferral for its duration.\n\nDeclaring a constraint deferred by default makes every transaction pay the debugging cost, which is rarely justified.\n\n## Where deferral genuinely helps\n\n**Swapping unique values.** The example above. With deferral, both updates run, the intermediate duplicate is tolerated, and the check at commit sees a valid state.\n\n**Circular foreign keys.** Two tables referencing each other cannot both be inserted first. Deferring the foreign key checks lets both inserts happen and validates at commit.\n\n**Bulk reordering.** Rewriting an entire ordered sequence passes through many transient duplicates.\n\n**Complex multi-table updates** where consistency holds at the end but not at each step.\n\nThe unifying property: the *final* state is valid, the path there is not, and the intermediate state is never visible to anyone else because it exists only inside an uncommitted transaction.\n\nThat last point is what makes deferral safe rather than a loosening of guarantees. Other transactions never observe the invalid state — isolation already prevented that. Deferral simply stops the database from objecting to something nobody could see.\n\n## The debugging cost\n\nThe real drawback, and the reason not to defer by default.\n\nWith immediate checking, a violation is reported by the statement that caused it. You know exactly which operation was wrong.\n\nWith deferred checking, the error arrives at commit. The transaction may have executed dozens of statements, and the error says a constraint was violated without saying which statement did it. Attributing it means reasoning about the whole transaction.\n\nIn a long transaction with several deferred constraints, that becomes genuinely difficult — which is why \"deferrable initially immediate\" is the right shape: you get normal error locality, and the ability to defer where a specific transaction needs it.\n\n## Alternatives worth knowing\n\nDeferral is not the only answer to the swap problem.\n\n**Use a temporary value.** Move the first row to an unused position, move the second, then move the first into place. Three statements, no deferral, and it works everywhere.\n\n**Leave gaps in the ordering.** Use positions 10, 20, 30 rather than 1, 2, 3, so reordering rarely needs a swap. Widely used and it degrades over time as gaps fill.\n\n**Use a fractional or sortable ordering key.** Insert between two items without touching either. More sophisticated and avoids the problem structurally.\n\nFor circular foreign keys, the alternative is usually to reconsider the model — mutually required references frequently indicate the two tables should be one, or that one reference should be nullable.\n\n## Common mistakes\n\n- **Deferring by default.** Loses error locality for every transaction.\n- **Long transactions with deferred constraints.** Errors become hard to attribute.\n- **Assuming deferral relaxes isolation.** It does not; the invalid state was never visible.\n- **Using deferral where a temporary value would do.** More portable and clearer.\n- **Not checking ORM support.** Frameworks may not expose the control.\n- **Circular foreign keys as a design.** Usually a modelling problem.\n\n## FAQ\n\n**Does deferral weaken guarantees?**\nNo. The constraint still holds for anything committed, and the intermediate state was never visible to other transactions. It changes when the check runs, not what is guaranteed.\n\n**Can I defer any constraint?**\nIt must be declared deferrable when created. Changing an existing constraint means dropping and recreating it, which is a migration.\n\n**Do all databases support this?**\nSupport and syntax vary; foreign keys are more widely deferrable than unique constraints. Check your engine.\n\n**Is this related to isolation levels?**\nDifferent mechanism. Isolation governs what transactions see of each other; deferral governs when a transaction's own constraints are checked — see [database isolation levels](/blog/database-isolation-levels-2026).\n\n## Where to go next\n\nFor constraint types generally, read [check constraints](/blog/check-constraints-explained-2026). For overlap rules, [exclusion constraints](/blog/exclusion-constraints-explained-2026), and for the isolation model, [database isolation levels](/blog/database-isolation-levels-2026).","hue":320,"metaTitle":"Deferrable Constraints: Checking at Commit Time","metaDescription":"Why swapping unique values fails, how deferrable constraints allow temporary violations within a transaction, and the debugging cost of deferred errors.","keywords":["deferrable constraints","deferred constraint checking","unique swap problem","circular foreign keys","transaction constraint timing","commit time validation","sql constraints","database design 2026"],"tldr":["**Constraints normally check per statement** — so a valid end state reached through an invalid middle fails.","**Deferring moves the check to commit** — the intermediate state is never externally visible anyway.","**Errors arrive at commit, not at the statement** — which makes them harder to attribute.","**Deferrable initially immediate is the safer default** — deferrable when needed, immediate otherwise.","**Skip:** deferring constraints by default. Immediate checking gives better error locality."],"readTime":"4 min read","wordCount":858},"related":[{"slug":"check-constraints-explained-2026","title":"Check Constraints: Rules the Database Enforces for You","excerpt":"Validation in application code holds until something else writes to the table. A check constraint holds for every writer, including the console session during an incident.","category":"code","date":"January 9, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":307},{"slug":"soft-delete-patterns-2026","title":"Soft Delete: The Flag That Infects Every Query","excerpt":"Adding a deleted_at column looks like the safe choice. Then every query needs a filter, every unique constraint breaks, and one forgotten WHERE clause leaks deleted data.","category":"code","date":"August 13, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":302},{"slug":"what-is-two-phase-commit-2026","title":"What Is Two-Phase Commit in 2026: The Protocol Explained","excerpt":"Two-phase commit is the classic protocol for making a transaction atomic across multiple nodes. Here is the 2026 guide to how prepare and commit phases work, why it can block, and when a saga is the better call.","category":"code","date":"July 24, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":290}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"bulk-insert-performance-2026","title":"Bulk Insert Performance: Loading Millions of Rows","excerpt":"Inserting a million rows one statement at a time takes hours. The same data loaded properly takes minutes, and the difference is almost entirely round trips and index maintenance.","category":"code","date":"January 2, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":308},"older":{"slug":"fireproof-safe-guide-2026","title":"Fireproof Safes: What the Rating Actually Means","excerpt":"A safe rated to protect paper will not protect a hard drive, because the temperature that chars paper destroys electronics long before. The ratings are specific and frequently misread.","category":"guides","date":"January 2, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":165}}