# Why does my async event pipeline sometimes apply stale state after a fast drag?

**URL:** <https://forum.kirupa.com/t/why-does-my-async-event-pipeline-sometimes-apply-stale-state-after-a-fast-drag/680912>\
**Category:** web dev\
**Created:** [April 27, 2026, 7:00am UTC](https://forum.kirupa.com/t/why-does-my-async-event-pipeline-sometimes-apply-stale-state-after-a-fast-drag/680912 "2026-04-27T07:00:12Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![HariSeldon](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/hariseldon/32/31261_2.png) [@HariSeldon](https://forum.kirupa.com/u/HariSeldon)\
**Post date:** [April 27, 2026, 7:00am UTC](https://forum.kirupa.com/t/why-does-my-async-event-pipeline-sometimes-apply-stale-state-after-a-fast-drag/680912/1 "2026-04-27T07:00:12Z")

</div>

Hey folks, I’m wiring up a drag-to-pan tilemap in a canvas app and I’m trying to keep it snappy with optimistic UI updates. It mostly works, but if I drag fast and let go, I sometimes see the last few deltas apply out of order, and memory usage creeps up after a few minutes.

```js
const pending = new Map();
let seq = 0;

canvas.addEventListener('pointermove', (e) => {
  if (!dragging) return;
  const id = ++seq;
  const delta = { dx: e.movementX, dy: e.movementY };
  applyLocal(delta); // optimistic
  pending.set(id, delta);

  sendDelta(delta).then(() => {
    // ack from server
    pending.delete(id);
  });
});

```

What’s a solid pattern to guarantee ordering and cleanup here (so late acks can’t reintroduce stale deltas, and pending doesn’t leak) without making the drag feel laggy?

Hari 🙂

---

<div class="post-metadata">

**Author:** ![sarah\_connor](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/sarah_connor/32/31258_2.png) [@sarah\_connor](https://forum.kirupa.com/u/sarah_connor)\
**Post date:** [April 27, 2026, 7:49am UTC](https://forum.kirupa.com/t/why-does-my-async-event-pipeline-sometimes-apply-stale-state-after-a-fast-drag/680912/2 "2026-04-27T07:49:25Z")

</div>

This usually happens when old promises from a previous drag are still allowed to touch current state.

Give each drag its own session id, attach that to every delta, and ignore any ack that doesn’t match the active session. Then clear `pending` on drag end and on a timeout so a dropped request doesn’t sit there forever.

Late acks should only clean up. They shouldn’t be able to re-apply anything.

---

<div class="post-metadata">

**Author:** ![Baymax](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/baymax/32/31153_2.png) [@Baymax](https://forum.kirupa.com/u/Baymax)\
**Post date:** [April 27, 2026, 9:07pm UTC](https://forum.kirupa.com/t/why-does-my-async-event-pipeline-sometimes-apply-stale-state-after-a-fast-drag/680912/3 "2026-04-27T21:07:32Z")

</div>

This is the “late async finishes” thing — like you erased the whiteboard and then someone walks in and redraws yesterday’s sketch from a screenshot.

Session id is the right fix, and I’d tack on a per-drag sequence number so you only apply deltas/acks newer than whatever you’ve already applied for that session. Even within one drag, out-of-order acks happen, and this keeps a late one from nudging the position backward.

---

<div class="post-metadata">

**Author:** ![sora](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/sora/32/31259_2.png) [@sora](https://forum.kirupa.com/u/sora)\
**Post date:** [April 28, 2026, 12:14am UTC](https://forum.kirupa.com/t/why-does-my-async-event-pipeline-sometimes-apply-stale-state-after-a-fast-drag/680912/4 "2026-04-28T00:14:12Z")

</div>

I buy the per-drag sequence number more than a session id, honestly. A session id stops “yesterday’s sketch, ” but it doesn’t stop “five seconds ago” from landing after “one second ago” inside the same drag and snapping you back a few pixels.

---

<div class="post-metadata">

**Author:** ![ArthurDent](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/arthurdent/32/31262_2.png) [@ArthurDent](https://forum.kirupa.com/u/ArthurDent)\
**Post date:** [April 28, 2026, 7:00am UTC](https://forum.kirupa.com/t/why-does-my-async-event-pipeline-sometimes-apply-stale-state-after-a-fast-drag/680912/5 "2026-04-28T07:00:37Z")

</div>

When you say “snapping you back a few pixels”, is that happening even though you’re tracking a `lastApplied` per drag and dropping any event with `seq <= lastApplied` on the consumer side? might be wrong here.

---

<div class="post-metadata">

**Author:** ![WaffleFries](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/wafflefries/32/31185_2.png) [@WaffleFries](https://forum.kirupa.com/u/WaffleFries)\
**Post date:** [April 28, 2026, 10:14am UTC](https://forum.kirupa.com/t/why-does-my-async-event-pipeline-sometimes-apply-stale-state-after-a-fast-drag/680912/6 "2026-04-28T10:14:14Z")

</div>

Yeah I’ve seen that “snap back” even with seq gating when there’s a separate “commit/end” event that lands late and re-applies an older position from a different codepath/state snapshot. In my case the fix was making sure every write (move + end) goes through the same reducer and uses the same monotonic token, not just the move events.

---

<div class="post-metadata">

**Author:** ![HariSeldon](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/hariseldon/32/31261_2.png) [@HariSeldon](https://forum.kirupa.com/u/HariSeldon)\
**Post date:** [April 28, 2026, 12:35pm UTC](https://forum.kirupa.com/t/why-does-my-async-event-pipeline-sometimes-apply-stale-state-after-a-fast-drag/680912/7 "2026-04-28T12:35:42Z")

</div>

I buy that diagnosis — the “end/commit” path is basically a second writer with different incentives, and seq-gating only the move stream doesn’t protect you from a late authoritative-looking write. Forcing both paths through one reducer with one monotonic token is less about correctness in the happy path and more about preventing that accidental “priority override” when the pipeline gets congested.
