Use optimistic concurrency control to prevent lost updates: give each editable record a version, require saves to reference that version, and reject stale changes. Preserve the rejected draft so the user can compare it with the latest record and decide what to keep.
This approach suits business applications where people edit shared records occasionally, such as product descriptions, support assignments or campaign settings. It needs both server enforcement and a usable recovery screen. A warning that someone else is editing can help, but the save operation still needs protection.
1. Identify where a successful save can lose work
Consider a proposed product administration workflow. Two colleagues open the same product. One changes its description and saves. The other changes its category, then saves the older form, including the old description. If the application accepts every field unconditionally, the description change disappears even though both users saw a success message.
The second user did nothing unusual. Their browser simply held an outdated copy. Automated imports, background jobs and separate browser tabs can create the same conflict.
Start by listing every route that changes the record. Include administrative screens, mobile applications, integrations and scheduled tasks. For each, ask whether it submits a whole record, selected fields or a specific operation. Updating fewer fields can reduce accidental replacement, but two writers can still change the same field.
Choose the record boundary carefully. A product description and its translation might warrant separate versions if people edit them independently. Fields that must remain consistent should share an appropriate validation boundary. Treat this as a business rule review, not just a database design choice.
2. Use ETags and If-Match for conditional saves
HTTP provides a mechanism for this. The server returns an ETag identifying the representation the user loaded. The application sends that tag back through If-Match when saving. A mismatched condition produces a 412 Precondition Failed response, rather than an unconditional overwrite. MDN's If-Match reference explains both this lost-update use case and the requirement for strong comparison.
Agree on the contract before implementation. A successful edit should return the saved representation and its new validator. A stale edit should leave the existing record intact. Define how the server handles missing validators so an older client cannot silently bypass protection.
Weak ETags cannot satisfy If-Match. An existence-only condition also cannot establish that the user edited the current version. Ask the engineering team to demonstrate that a meaningful change invalidates the previous validator and that an unchanged representation keeps consistent validator behaviour.
The browser does not automatically turn an ordinary save button into a protected edit. Application code must send the condition, and the server must enforce it. Cross-origin applications also need their browser access configuration checked so the client can read the validator and send the required header.
3. Make version checking and writing one protected operation
A version check can still fail if the server reads the current version, compares it, then writes later without protection. Another request can slip between those steps. The comparison and update must operate atomically.
One implementation pattern uses an explicit revision value. The database update targets the record identifier and expected revision together, changes the permitted fields, and advances the revision in the same operation. The application checks whether the update actually matched. Engineers must also distinguish conflicts from deletion or access failures without exposing records the requester cannot view.
Laravel's query builder documentation documents constrained updates and affected-row counts. These are useful building blocks for such a design, but a normal model save does not establish the complete concurrency contract by itself.
Short database locks can support operations that read and change related state within a transaction. They do not, by themselves, protect the minutes between opening a browser form and submitting it. Avoid holding a database transaction open while someone decides what to type.
Apply the revision rule to imports and background jobs too. A protected browser screen offers limited protection if a nightly synchronisation can replace the same fields without checking their current state. Review these writers alongside the broader concerns in state management and event-driven web application architecture.
4. Choose a conflict policy that fits the work
Rejecting a stale save is only the start. The right recovery depends on whether edits are independent and whether users can reasonably compare them. The following table proposes review criteria, not universal defaults.
| Editing situation | Proposed policy | What to verify |
|---|---|---|
| Occasional edits to a shared settings record | Reject stale saves and offer comparison | The draft survives and users can identify changed fields |
| Independent fields with simple relationships | Consider a controlled field merge | Compare the original, current and proposed values, then validate the combined record |
| Several fields jointly determine a workflow state | Reject and require a fresh review | Individually valid fields cannot form an invalid combination |
| Frequent simultaneous editing of long documents | Evaluate a dedicated collaborative editing design | Test merge behaviour, reconnection and history requirements |
A controlled merge needs the original values the user loaded, the current saved values and the proposed changes. Comparing only the two latest versions cannot reliably show which writer changed which field.
Even edits to different fields can conflict in meaning. A changed category might make a proposed description inappropriate. Define which combinations require human review, and rerun business validation after any merge. Where conflicts are rare, a clear comparison screen may be easier to maintain than automatic merging.
5. Preserve the draft and explain the recovery
After a rejected save, keep the user's input visible. Explain that the record changed after they opened it and that their proposed changes have not been saved. Offer a way to inspect the latest values without discarding the draft.
For short records, show differences beside the affected fields. For longer text, provide a readable comparison. Label actions according to their consequences: load the latest record, revise the draft, or discard the draft. Avoid an ambiguous retry button that merely submits the same stale request again.
MDN's conditional request guide describes notifying users or presenting differences after a rejected update. The interface still needs a product-specific decision about what users may retain and resubmit.
Make save feedback accessible. A message inserted without moving focus should be available to assistive technology through appropriate roles or properties, as explained in W3C's guidance on status messages. A comparison dialog needs its own keyboard and focus handling. Test both the successful save and conflict recovery with a screen reader.
Draft retention also needs boundaries. Decide whether drafts survive navigation, how long they remain and what happens on a shared device. Do not add persistent browser storage without reviewing what the form contains.
6. Test competing saves before rollout
Use this practical checklist for one representative editing flow:
- Open the same record in two independent sessions, save one change, then submit the stale form.
- Confirm that the second save changes no protected data and keeps its draft available.
- Submit competing saves concurrently to test the server's atomic protection.
- Test overlapping field edits, independent edits and deletion while a form remains open.
- Exercise imports, background jobs and older clients that omit version information.
- Confirm that successful saves return a usable new version, including after conflict recovery.
- Check keyboard access, screen reader announcements and recovery after a lost connection.
Common mistakes include checking versions only in the browser, silently fetching a fresh validator before resubmitting an old draft, and displaying success before the server confirms the write. Automatic retries must not convert a rejected edit into an overwrite. A lost response also deserves separate handling: the server may have saved successfully even though the browser did not receive confirmation.
Track conflicts separately from ordinary failures. Record enough context to identify the editing flow and writer type without logging complete sensitive drafts. Review whether conflicts reflect a missing integration rule, an awkward record boundary or people routinely sharing the same work.
For the next step, select one shared record and write its save contract and recovery behaviour together. Review those acceptance criteria within your web platform delivery process. Release only after the competing-save test proves that the later stale request preserves the earlier change and gives its user a workable way forward.
India
UAE
KSA
USA
UK
Canada
Oman
Kuwait
Australia
Thailand