JavaScript can interleave asynchronous operations even when their callbacks execute on one thread. Two requests can read the same value, calculate different updates and each write a replacement. The later write may erase the earlier change.
This is a consistency problem, not evidence that JavaScript ran two statements simultaneously.
Reconstruct the interleaving
Imagine an account whose tags initially contain only “customer”:
| Step | Request A | Request B |
|---|---|---|
| Read | customer | customer |
| Calculate | customer, active | customer, subscribed |
| Write | customer, active | waits |
| Write | finished | customer, subscribed |
“Active” disappears because B wrote a replacement based on its earlier read. This example is hypothetical and can be reproduced locally with controlled promises; it does not require a production outage.
The first diagnostic question is whether the application is sending a command—add a tag—or replacing a whole snapshot. Those are different operations with different conflict behavior.
Let the database own shared updates
For a counter in PostgreSQL, a parameterized statement such as this expresses the operation directly:
UPDATE accounts
SET login_count = login_count + 1
WHERE id = $1
RETURNING login_count;
Assume login_count is non-null and the account exists. Check the affected row count and apply authorization separately. PostgreSQL’s Read Committed behavior lets a concurrent updater wait and then operate on the updated row; it is not a general guarantee for every multi-row workflow. The transaction isolation documentation explains the boundary.
For a read–calculate–write workflow, alternatives include a transaction with appropriate locking or a version column checked during UPDATE. A version mismatch is a conflict to handle, not a successful write. Bound retries, and keep non-idempotent side effects out of a retried calculation.
A queue for one JavaScript process
Local serialization can coordinate work that lives entirely in one process. This implementation returns each operation’s original result or rejection, while keeping a separate completion tail so a failure does not poison the next task:
export function createKeyedQueue() {
const tails = new Map();
function run(key, task) {
const previous = tails.get(key) ?? Promise.resolve();
const result = previous.then(task);
const tail = result.then(() => undefined, () => undefined);
tails.set(key, tail);
void tail.then(() => {
if (tails.get(key) === tail) tails.delete(key);
});
return result;
}
return { run, pendingKeys: () => tails.size };
}
Different keys can progress independently. Completed keys leave the map. Callers must await or catch the returned promise.
This queue does not coordinate another process, container or serverless instance. It also has no admission limit: a caller can enqueue more work than the process should retain. Use it for a bounded local workload with a separate capacity policy, not as a replacement for database coordination.
Test the failure you are trying to prevent
Hold the first task behind a manually controlled promise. Enqueue another task with the same key and verify it has not started. Release the first, check ordering, then repeat with a rejection and verify the following task still runs. Finally assert that the completed key was removed.
For an actual database workflow, run concurrent writes against an isolated test database and verify the final business invariant. An HTTP load generator’s virtual-user count is not the number of requests: repeated iterations may send many requests per user.
Treat the cache as another consistency boundary
Committing a database transaction does not atomically update an unrelated cache. Decide whether readers may see stale values, how invalidation retries work and which version wins when updates arrive out of order.
Write the invariant before choosing a lock: “Every accepted increment is represented once” is more useful than “Make this function thread-safe.” It identifies the shared storage boundary and gives the test something concrete to prove.

Sardar Mukhshaf
Full-Stack & DevOps Engineer
I build scalable web applications, secure backend systems, and production-ready cloud infrastructure. My engineering focus spans interface design, API architecture, deployment pipelines, and observability. Follow for more deep dives into system architecture, performance engineering, and emerging technologies.
More from the practice
Keep the useful ideas close.
Follow new articles with your preferred RSS reader.
Open the RSS feed ↗


