Skip to content

[BUG] Concurrent task status updates cause race condition, resulting in lost writes #1660

Description

@anshul23102

Summary

When a user rapidly toggles a task's completion status (or two browser tabs update the same task simultaneously), the PATCH /api/tasks/:id endpoint performs a read-then-write without any concurrency control. The last write wins, silently discarding the intermediate state update.

Root Cause

In backend/controllers/taskController.js, the update uses findByIdAndUpdate with $set, which is not atomic for conditional updates:

const task = await Task.findById(taskId);
task.status = newStatus;
task.updatedAt = Date.now();
await task.save();

Two concurrent requests can both read the same document state, then both write, with the second write overwriting the first.

Impact

Users lose task state changes silently. No error is shown; the UI appears to accept the change, but the server reverts it on the next fetch.

Expected Behavior

Status updates should be atomic. Using MongoDB's findOneAndUpdate with version-based optimistic locking or an atomic $set prevents lost updates.

Proposed Fix

Replace the read-then-write pattern with:

const updated = await Task.findOneAndUpdate(
  { _id: taskId, userId, __v: expectedVersion },
  { $set: { status: newStatus }, $inc: { __v: 1 } },
  { new: true, runValidators: true }
);
if (!updated) {
  return res.status(409).json({ error: "Conflict: task was modified by another request" });
}

Enable optimisticConcurrency: true in the Mongoose schema options.

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendIssues related to server-side, database logic or APIsbugSomething isn't workingfrontendIssues related to UI/UX

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions