An optimistic interface shows the result of an action before the server has answered, and a server answer is still not the same thing as a committed database change. A screen can say a todo is complete while the request is still in flight, after the request has failed, or after the database has committed but before a later read returns the new value. Those are four separate facts, and a front end can observe only some of them directly.
Four states that interfaces often collapse into one “saved” label
When a user changes data, four questions get answered at different times:
As an Amazon Associate I earn from qualifying purchases.
- What the UI renders right now. This may be a prediction of the outcome, not a record of it.
- Whether the request is pending or has returned. The client knows this, but only from its own point of view.
- Whether the database transaction committed. Only the database session that ran the transaction knows this for certain, and the client sees it only indirectly through the server’s response.
- What a subsequent read observes. A later query may use a different snapshot, a replica, or a cache, so it may not show the same thing the commit produced.
Most bugs in this area come from treating the first state as if it answered the other three.
Why does the UI show a change before it’s saved?
Because showing the change immediately makes the interface feel responsive. React’s useOptimistic hook, documented in the React reference, is built around this idea. While an Action is running, the component renders an optimistic value. When the Action completes, React renders the base value again, which is whatever the component’s real state says at that point. If the Action succeeded and the base state has been updated, the user sees the new value. If it failed, the optimistic value disappears and the old value returns.
#1 Best Overall
The React documentation’s own example makes the failure case concrete: when a delete fails, the item reappears. That is the intended behavior, not a glitch. The optimistic value was only ever a temporary presentation.
The practical consequence is that a pending optimistic value should not be labelled as final. If your product copy, status badges, or confirmation messages imply that the change is persisted, they are claiming something the client has not yet learned.
What a database commit actually means
A commit is a separate event that happens in the database, after the statements in a transaction have run. The PostgreSQL 18 documentation for COMMIT describes it simply: “COMMIT commits the current transaction.” The Transactions chapter adds the visibility rule that matters for application developers: “The intermediate states between the steps are not visible to other concurrent transactions.” Until the commit, the changes made inside an open transaction are invisible to other sessions. At commit, they become visible together.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Durability is a further property. With PostgreSQL’s default settings, a successful commit is durable against a crash. That guarantee depends on configuration, covered below.
| State | Where it lives | What it establishes | What it does not establish |
|---|---|---|---|
| Optimistic value | Client memory, component state | The interface expects the change to succeed | That any request was sent, accepted, or stored |
| Pending request | Client network layer and server handler | A request is in progress | Whether the server executed the change or will |
| Server success response | API layer, after the handler returns | The handler believes the operation succeeded | Anything about durability unless the handler waited for the commit to return |
| Committed transaction | Database, after COMMIT returns successfully |
The changes are visible to other sessions and, under default settings, durable | That every later read in every session uses the same view |
| Subsequent read | Query, cache, or replica | What that particular read returned at that moment | The state of the primary database at some other time |
Walking one action through the timeline
A todo-completion action gives a useful reference sequence. Each transition is a point where the two sides can disagree.
- The user clicks the checkbox. The client updates the optimistic value and, if relevant, shows a pending marker.
- The client sends the request to the server. The request is pending from the client’s perspective.
- The server handler opens a transaction and runs its statements. The changes are not yet visible to other sessions.
- The server issues
COMMIT. If it returns successfully, the changes become visible to other transactions. - The server sends its response. The client receives a success or failure.
- On success, the client replaces or confirms the optimistic value using the server’s data. On failure, it rolls back the optimistic value or refetches authoritative state and shows an error.
- A later read, through the same or another path, returns whatever it sees under its own snapshot and cache rules.
If your code describes the state as “saved” at step 2, it is claiming something that has not happened yet.
Why the screen and the database diverge
The request fails or times out
A network error, a timeout, or a server crash can leave the client without an answer. The client must treat this as unknown, not as “not saved.” The server may have committed before the connection dropped. A retry from the client can then apply the change twice unless the operation is idempotent, for example through a client-generated key that the server checks before writing. Whether your application does this is an implementation question; the database cannot infer it for you.
The success response is sent before the commit finishes
An application can return HTTP 200 before the transaction has actually committed, for example by queueing the write, using a background worker, or sending the response inside the transaction block. In that case the success message reports intent, not a durable result. The safe ordering is to send the success response only after the database call that issues COMMIT has returned without error.
async function completeTodo(req, res) {
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query(
'UPDATE todos SET done = true WHERE id = $1 AND user_id = $2',
[req.params.id, req.user.id]
);
await client.query('COMMIT');
} catch (err) {
await client.query('ROLLBACK');
return res.status(500).json({ error: 'Change was not saved' });
} finally {
client.release();
}
res.json({ ok: true });
}
The example is a sketch rather than a complete service. It assumes the pg client and a pool created elsewhere, and it does not handle idempotency or retries.
Another writer changes the data while the request is pending
While the client holds an optimistic value, another user or process may modify the same record. When the base state is replaced, the optimistic change may be recalculated against stale assumptions. React recommends a reducer-based approach for optimistic updates that must recalculate from changed props, such as a list another user has modified. Without that, the interface can show a combination of your change and their change that never existed in the database.
A later read sees a different snapshot or cache
PostgreSQL’s Read Committed isolation level, which is the default, gives each statement a snapshot taken at the start of that statement. Successive SELECT statements in one transaction can therefore see different data when another transaction commits between them. A commit does not promise that every later query in every session returns the same thing. Caches and replicas add further gaps: a read served from a cache or a lagging replica can show the state from before your commit, even though the primary has the new value. Do not infer replica freshness or cache state from a front-end success message.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitchesWhy did my UI update revert after the request failed?
This is the expected sequence when the optimistic value was never backed by a committed change. The client should not treat the revert as data loss, because the data was never changed. The correct response is to tell the user what happened.
TanStack’s optimistic-update documentation, in its v3 guide, states the basic risk plainly: “When you optimistically update your state before performing a mutation, there is a chance that the mutation will fail.” In TanStack Query’s v3 pattern, the recovery is to snapshot the previous state before the mutation, restore it on error, and refetch from the server when the mutation settles. The same pattern appears in later versions under different option names, so check the documentation for the version you run.
Recovery has three parts:
- Roll back the presentation. Restore the previous value or the last value confirmed by the server.
- Refetch authoritative state when the outcome is unclear. After a timeout, a refetch is more reliable than assuming either outcome.
- Explain the error. Leaving a reverted or still-optimistic item on screen without a message makes the failure look like a bug in the data.
Durability depends on configuration
PostgreSQL’s asynchronous-commit setting, synchronous_commit, changes what a successful commit guarantees. When it is turned off for a session or transaction, the server can report a commit before its WAL records are flushed to disk. The PostgreSQL documentation describes a crash window in which changes from such a transaction can be lost, even though the commit was reported successfully. The documentation notes that the window is bounded, and it is related to the WAL writer’s delay. The data is still consistent after a crash in this mode; what can be lost is the most recent commits.
Because of this, the statement “the database committed it” means different things under different settings. Document the configuration of the system you are describing, and do not assume every deployment has the same durability. The PostgreSQL 16, 17, and 18 documentation describes these behaviors with the same core concepts, so the guidance applies across those versions, but check the release notes when you upgrade.
Symptoms and checks
- The item reverts after a refresh. The client showed a value the server never committed, or the commit happened and a later read used a stale path. Compare the server log, the database row, and the read path.
- The item shows as done, but the database row says otherwise. The client may have replaced its state with a success it received before the commit completed, or a retry overwrote a later change.
- Two users see different values for the same record. Check isolation level, cache TTLs, and whether reads go to a replica.
- The item flickers between states. Check whether the base value was replaced while the optimistic value was active, and whether the reducer recalculates correctly.
- A recent change disappears after a crash. Check
synchronous_commitfor the session or transaction that wrote the change.
Wording that stays accurate
Use labels that match the state the client actually knows. “Saving…” fits a pending request. “Saved” should wait for a confirmed server response that reflects a completed commit. “Could not save. Your change was not applied.” fits a known failure. For an unknown outcome, such as a timeout, “We could not confirm this change. Refresh to check” is more accurate than either “saved” or “failed.” The point is to avoid claiming more than the client has observed.
These labels do not replace the server’s guarantee. They describe what the user can rely on at each point, and the server should only report success once its own commit has returned without error.
Sources for this article are the React documentation for useOptimistic, the TanStack Query v3 documentation for optimistic updates, and the PostgreSQL 16, 17, and 18 documentation for COMMIT, transactions, Read Committed isolation, and asynchronous commit. A community discussion on r/react illustrates a common way developers describe the question; it is one developer’s account and is not evidence of how common the problem is. This article does not establish how any particular application handles acknowledgments, caches, replicas, retries, or idempotency, which you need to verify in your own system.
One limitation of the sources is worth stating. The official documentation explains how these mechanisms are meant to behave, but it does not provide measured rates of optimistic-update failure or database inconsistency in real applications, so this article makes no such claim.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




