Implement optimistic UI for a like button with rollback on failure

Tests state management and error recovery in async UIs. A strong answer mutates state instantly, fires the API in parallel, keeps a rollback snapshot, and reverts with a toast on failure. Red flag: waiting for the network or skipping revert logic.
WHAT THIS TESTS: This question probes your ability to manage client-side state through an asynchronous boundary without degrading perceived performance. The interviewer cares about transaction-like thinking in the UI layer, specifically how you balance immediate feedback with eventual consistency and what happens when the network betrays that optimism.
A GOOD ANSWER COVERS: A good answer hits four things in order. First, mutate the store immediately on click: toggle the liked boolean and increment the visible count so the button flips within a single frame. Second, fire the POST request in the background without blocking further interaction. Third, preserve the pre-click state or implement a pending-transaction queue so you have a clean rollback path if the server rejects the request or times out. Fourth, handle the error path explicitly: catch the exception, revert the store to the previous liked state and count, and surface a transient toast or inline message so the user knows the action failed. Strong candidates also mention race-condition guards, such as ignoring stale responses if the user toggles the button rapidly, and optionally sending an idempotency key with the request.
COMMON WRONG ANSWERS: Red flags include describing a pessimistic flow where the UI waits for the API to return before updating, which misses the point entirely. Another anti-pattern is mutating global state optimistically but having no rollback strategy, leaving the like stuck in the wrong state after a 500 error. Blocking the button with a loading spinner during the request also signals weak UX intuition. Finally, silently swallowing the error without reverting or notifying the user destroys trust in the interface.
LIKELY FOLLOW-UPS: Interviewers often push deeper by asking how you would debounce or deduplicate rapid clicks to avoid double POSTs. They may ask what happens if the user navigates away mid-request, how you would persist the pending mutation for retry, or whether you would use a local optimistic layer versus mutating the canonical store directly. Accessibility follow-ups are common too, such as how screen readers should announce the reverted state.
ONE CONCRETE EXAMPLE: Imagine a Pinia store holding a posts array. The user clicks like on post ID 42. The action immediately commits a mutation that sets post 42 liked to true and increments the count by one. It then caches the previous liked value and count in a closure or a separate pending map. The action fires a fetch POST to slash api slash posts slash 42 slash like. On resolve, it simply clears the pending entry. On reject, it commits a revert mutation using the cached values and dispatches a toast notification. If the user clicks again while the first request is in flight, the UI toggles optimistically but the second request is queued or deduplicated with an idempotency key to prevent a double increment on the server.
Source: Crystallize
Read the original → crystallize.com
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.