Two People Editing the Same Record: How to Fix It
The second save wins and the first person's work disappears with no error. The most common data-loss bug in AI-built internal tools, and how to close it.
Two People Editing the Same Record: How to Fix It
Ask an AI builder for an edit form and you will get one that silently destroys work. Not because the code is wrong, but because nobody specified what should happen when two people editing the same record hit save within a minute of each other. The default behaviour, in essentially every generated CRUD app, is that the second save wins and the first person's changes vanish with no error, no warning, and no record that they ever existed.
This is the single most common data-loss bug in AI-built internal tools, and it is invisible in testing because one developer clicking around never collides with themselves. Here is what the generated code actually does, and three fixes ranked by effort.
What your app is doing right now
The generated update almost always looks like this. Read it as the customer service screen it usually is.
-- what the AI wrote
update customers
set name = $1,
email = $2,
notes = $3
where id = $4;Now walk two people through it. Amira opens customer 501 at 10:00 and starts rewriting the notes field. Ben opens the same customer at 10:02, corrects the email, and saves at 10:03. Amira saves at 10:05. Amira's form still holds the email as it was at 10:00, so her save writes the old email back over Ben's correction. Ben's fix is gone. Nobody is told.
The bug is not that the second write wins. It is that the second write includes stale copies of fields the second person never looked at.
That distinction matters, because it explains why this survives so long in production. Amira did nothing wrong and saw nothing unusual. Ben notices a week later that the email is wrong again and assumes he forgot to hit save. The bug gets attributed to human error for months.
Three fixes for two people editing the same record
Fix | Effort | What the user sees | Use when |
|---|---|---|---|
Update only changed fields | Low | Nothing, but most collisions stop mattering | Wide forms where people usually edit different fields |
Version column with a conflict error | Medium | A clear message that someone else changed this | Anything where a silent overwrite would be serious |
Field-level merge or live collaboration | High | Other people's cursors and changes as they happen | Documents and shared text, rarely worth it for records |
Fix one: stop sending fields nobody touched
The cheapest change is to send only the fields the user actually modified, rather than the whole form. Amira's save then contains only the notes column, Ben's contained only the email, and the two no longer overwrite each other at all.
This eliminates most real-world collisions for about twenty minutes of work, and it is usually the right first move. It does not help when two people edit the same field, and it gives the user no signal that anything happened, so it is a mitigation rather than a solution.
Fix two: a version column, which is the one most apps should ship
Add an integer that increments on every write. The client sends back the version it loaded. If that version no longer matches, the write is refused and the user is told.
alter table customers add column version integer not null default 1;
-- the update, now conditional
update customers
set name = $1,
email = $2,
notes = $3,
version = version + 1
where id = $4
and version = $5 -- the version the client loaded
returning version;Zero rows returned means somebody else saved first. The API should answer that with HTTP 409 Conflict, which exists precisely for this case, and the interface should then show the user what changed rather than simply refusing. This is optimistic concurrency control, and it is called optimistic because it assumes collisions are rare and only pays a cost when one actually happens.
The part people get wrong is the error screen. "Someone else has edited this record, please reload" throws away the user's work and teaches them to copy their text before every save. Show both versions of the conflicting field and let them choose. Keeping their draft in the page while they decide is the whole difference between a fix and a new annoyance.
Fix three: live collaboration, which you probably do not need
Character-level merging, the kind behind shared documents, is a genuinely hard problem with mature libraries solving it. For a record with fifteen fields it is the wrong tool. Reach for it only when the thing being edited is a long free-text document that two people legitimately work on simultaneously.
What a database transaction does and does not solve
A common instinct is to wrap the update in a transaction and consider the matter closed. Transactions protect you from interleaved writes inside a single operation. They do not protect you here, because Amira's and Ben's edits are separated by minutes of human thinking time and are two entirely separate transactions, each perfectly correct on its own. Raising the isolation level does not help either. PostgreSQL's transaction isolation documentation is worth reading on exactly this point: the guarantees are about concurrent transactions, not about a user holding a form open.
One more reason to prefer the version column over cleverness: it degrades honestly. If you later add a background job, a bulk import, or a mobile client, each of them has to pass a version to write, so each of them either respects the rule or fails loudly. Solutions built into the user interface, such as warning banners or polling to see whether a record changed while the form was open, protect only the path they were built for and quietly leave every other writer free to overwrite. A constraint that lives in the database is the only kind that covers writers you have not thought of yet.
How to check whether your app has this bug
Open the same record in two browser windows, ideally two different browsers so sessions differ.
In window one, change field A. Do not save yet.
In window two, change field B and save.
Return to window one and save. Reload the record.
If field B has reverted to its old value and nothing warned you, you have the bug.
Run that test on every screen where two people could plausibly be working at once: customer records, order notes, inventory counts, anything with a status field. Counters are worth special attention, because an inventory quantity written as a whole number rather than an increment loses stock silently in exactly the same way. If you are choosing infrastructure now, the concurrency behaviour on offer is one of the things worth weighing when picking a database for an app you build with AI.
Make the fix stick when the AI regenerates the file
One practical warning. If you add a version column by hand and later ask the builder to change that same screen, there is a decent chance the regenerated update statement drops the version check, because nothing in the prompt said it mattered. Write the rule into your project instructions rather than trusting it to survive, and be aware of the ways a builder loses hand-written edits, which is the same class of problem one level up.
For the records where losing an edit would be genuinely costly, pair the version check with history. Version history gives users a way back, and an audit log tells you who wrote what when the argument starts. Neither prevents the overwrite, so they belong alongside the version column rather than instead of it.
Frequently asked questions
Can I just lock the record while someone is editing it?
Pessimistic locking works in controlled environments and fails on the web, because browsers close, laptops sleep, and people go to lunch with a form open. If you try it, you need a lock timeout, a way to steal a lock, and a way to tell someone their lock expired. That is more machinery than the version column and it fails more visibly.
How likely is this really, with a small team?
More likely than it feels, because collisions cluster. Two people work the same record precisely when something is happening with it: a complaint, a rush order, a cancellation. The records where an edit gets lost are disproportionately the records where losing an edit matters.
Does this apply if I am using a spreadsheet-style backend?
Yes, and often worse. Spreadsheet-backed tools frequently write whole rows, which is the widest possible version of this bug. Check whether your backend supports a conditional write at all before assuming a version column is available to you.
What should the conflict message say?
Name the field, name the person if you know it, and show both values. "Ben changed Email to ben@example.com while you were editing. Keep yours, or keep theirs?" A user can answer that in a second. "Conflict detected" sends them to support.
How did this land?
About the author

Developer Advocate
Steve builds something with Swarmz every week and writes up what worked, what broke, and what he'd do differently. Tutorials and hands-on guides are his lane.


