Articles
Drag-and-Drop Coding Interview: Reordering, Keyboard Control, and Stable State
Prepare for drag-and-drop coding interviews with stable-ID reordering, keyboard and click controls, cancel behavior, focus, and safe server recovery.
The row moves correctly with a mouse, but its checkbox state jumps to another item. Keyboard users cannot move it. A server failure then restores an old array and erases a newer reorder. The animation was convincing; the state contract was not.
A drag-and-drop coding interview is strongest when you implement one stable-ID reorder operation and let pointer, keyboard, and click controls invoke it. Keep the committed order separate from the current gesture preview. Define cancellation, focus restoration, and server acknowledgment before adding smooth movement.
Official React, MDN, and W3C sources support the platform behavior cited below. No candidate reports establish an employer's prompt or question frequency; the reorder protocol and recovery choices are original engineering recommendations.
What exactly is the reorder contract?
A reorder operation changes the sequence of existing item identities without changing their membership or entity data. Its input should identify the moving item and intended destination unambiguously. Its output should preserve every unrelated item's relative order and keep focus associated with the moving identity.
For this original fixture, the committed order is ['a', 'b', 'c', 'd']. Entities live separately in an ID-keyed collection. The command is moveBefore(movingId, anchorId), where a null anchor means the end. This contract avoids ambiguous phrases such as “drop at index three” when the source removal changes index positions.
Clarify whether a filtered list can reorder the entire dataset or only the visible subset. If hidden items exist, the visible target may not specify a unique global order. For a first interview implementation, declaring a single complete list is a reasonable scope boundary; silently inventing behavior for filtered data is not.
Can you write the pure operation first?
Start with a pure helper before handling events. The following implementation treats missing identities and self-moves as no-ops, and requires the caller's initial IDs to be unique:
function moveBefore(order, movingId, anchorId = null) {
if (!order.includes(movingId)) return order;
if (movingId === anchorId) return order;
if (anchorId !== null && !order.includes(anchorId)) return order;
const next = order.filter(id => id !== movingId);
const target = anchorId === null
? next.length : next.indexOf(anchorId);
next.splice(target, 0, movingId);
return next;
}
Moving b before d yields ['a', 'c', 'b', 'd']. Moving d before a yields ['d', 'a', 'b', 'c']. Moving the first item to the end uses a null anchor. The target is resolved after removing the source, so there is no extra downward-index correction hidden in a handler.
Test membership preservation, unchanged input, no duplicates, and stable relative order of unaffected IDs. For a small interview list, linear scans are acceptable and easy to inspect. If the product has a huge ordered collection, discuss server-side ranking or indexed structures after establishing correctness, not instead of it.
Executed example evidence: On Node.js v26.9.0, the four-item fixture checked all 20 moving-ID/anchor combinations, including the null end anchor. Every result preserved membership, the original input, and unrelated-item order. Three invalid/self-move checks returned the original array. These are pure state tests; they do not establish working pointer capture, touch scrolling, keyboard focus, or assistive-technology behavior.
Return policy is part of the contract. This helper returns the original array for invalid moves; a different implementation might return a result explaining why the command was rejected. Whichever you choose, ensure UI feedback does not announce a successful move when no reorder occurred.
Why should keys never be visible indices?
React's list-rendering documentation explains that keys help associate rendered items with their identities as order changes. Using a visible index as a key confuses position with identity precisely when a sorting task changes positions.
In the original fixture, row b owns a local expanded panel and a focused action button. After moving b, those should still belong to b, not to whichever entity now occupies its old index. Use stable IDs for row keys, active identity, selected identity, and pending server commands.
This does not require every piece of state to live inside the row. Some product state may belong in the entity store. The important invariant is that a reorder changes order, not the meaning of state attached to an identity. Duplicate IDs invalidate both rendering and the pure helper's assumptions, so validate them at the data boundary.
Which state belongs to an active gesture?
An explicit interaction state can hold movingId, anchorId, input mode, and the committed version at pickup. Pointer coordinates, hover geometry, and a ghost preview are temporary. The canonical entity order should change only according to the chosen commit policy.
For this exercise, pickup enters a preview state. Moving over a valid gap changes the proposed anchor. Drop applies one pure command. Escape, pointer cancellation, or an invalid drop clears the preview and leaves committed order unchanged. Keeping these states distinct also makes an undo entry correspond to one committed move, not dozens of hover events.
If the moving item is removed during the gesture, cancel it. If the anchor disappears, either recompute a valid destination under an explicit policy or refuse the commit and explain why. Do not fall back to an arbitrary index from the old render.
Moving DOM nodes without updating the application model can produce a momentarily correct screen that the next render reverses. Conversely, changing the model on every pixel movement complicates cancellation and server synchronization. A preview layer offers a clear separation between feedback and ownership.
Native drag events or pointer events?
MDN's HTML Drag and Drop API describes native drag events and data transfer. It is useful when dragging data across document boundaries or accepting files. A custom sortable interaction may instead use pointer events for direct control over gesture behavior.
The Pointer Events documentation covers a shared event model and pointer capture. For a custom gesture, capture can help retain events while the pointer leaves the handle. You still need to handle cancellation and distinguish a drag from an ordinary tap or click.
Choose deliberately rather than claiming either API solves accessibility or touch behavior automatically. Define the handle, activation threshold, scroll interaction, and invalid-target behavior. For a vertical list, the row midpoint can define before versus after; convert that geometric decision into an anchor ID before calling the pure helper.
If dropping after the last row should be legal, render an explicit end gap. If a row contains links or text fields, avoid turning the whole row into a gesture surface that steals their normal behavior. A handle gives you a clearer interaction boundary and a place for instructions.
How do keyboard users perform the same move?
Provide controls that call the same command rather than implementing a second sorting algorithm. A simple baseline is an ordinary ordered list with Move up and Move down buttons per item. Disable impossible moves at boundaries, keep focus on the moved item's corresponding control, and announce its new position through a concise live region.
A richer pickup model can use an explicit reorder button, arrow keys to change the proposed position, Enter to commit, and Escape to cancel. Those are this exercise's chosen interactions, not a universal browser convention. Explain them visibly and test that ordinary navigation still works when no gesture is active.
The W3C rearrangeable listbox example provides a specific listbox pattern. Do not copy its roles onto a list of complex cards with nested buttons without checking the semantics. A listbox option and a rich interactive card do not necessarily share the same accessible structure.
Focus, selection, and grabbed identity are separate concepts. A selected row may remain selected after another row moves. A canceled gesture should restore the original order without dumping focus on the page body. Announcements should describe committed results or clearly labeled previews, not claim success before the operation is accepted.
Why are keyboard controls not the entire accessibility answer?
W3C's Dragging Movements guidance discusses alternatives that do not require dragging with a pointer. A keyboard path and a single-pointer alternative address different needs. Clickable Move up/down controls can support users who can click but cannot reliably perform a drag.
For the original list, the pointer gesture is an enhancement over those controls. This gives the implementation a usable baseline even if custom drag behavior is unavailable. It also creates a straightforward acceptance test: every valid pointer reorder should be achievable through a non-drag command.
Do not report accessibility compliance from a DOM role check alone. Verify keyboard-only behavior, visible focus, instructions, announcements, and interaction with the intended assistive technology. An interview demonstration can explain these checks without claiming a complete audited implementation.
How do optimistic updates avoid stale rollback?
Suppose move A is submitted at server version 10, then move B is submitted before A finishes. A later failure must not blindly restore the array captured before A, because that snapshot may erase B. Request completion order is not necessarily user action order.
One scoped interview design serializes committed moves through a queue. Each request carries an operation ID, stable moving/anchor IDs, and the expected server version. After acknowledgment, update the confirmed version and process the next operation. On a conflict, fetch the authoritative order and re-evaluate pending commands by identity.
A more responsive design can retain a confirmed base plus an ordered log of unconfirmed commands. The displayed order is the base with those commands replayed. Removing a rejected command then recomputes the projection instead of restoring a stale whole-array snapshot. This is an original recovery design, not behavior supplied by drag events.
For a concrete replay trace, start with a,b,c,d. Pending A moves b to the end; pending B moves d before a, producing d,a,c,b. If A is rejected, replay only B over the confirmed base: d,a,b,c. Restoring the pre-A array would lose B. The fixture checks this projection, without claiming a tested server protocol.
Specify what happens if a timeout leaves the write outcome unknown. Retry with the same operation identity when the server supports idempotency, or reconcile before issuing a new operation. Do not promise that aborting a browser request undoes a server reorder that already committed.
Which tests reveal a weak implementation?
Begin with pure tests: move upward, move downward, move to the end, self-move, missing source, missing anchor, and preservation of all unaffected IDs. Next test equivalent button and pointer commands. Their final arrays should be identical.
Interaction tests should cover cancel, focus after movement, a removed target, pointer leaving the handle, touch scrolling, and a row with an interactive child. Include a test where row-local state stays with its ID after reordering. For network behavior, resolve two requests out of order and demonstrate that a rejection cannot erase a later accepted move.
Virtualization is a separate complication: an offscreen anchor may have no DOM rectangle, and auto-scroll may change geometry during the gesture. Name that limitation if your implementation renders all rows. Do not imply a working small-list demonstration solves every virtualized or cross-list product.
Practice one command across input modes
| Practice question | Topic-specific follow-up |
|---|---|
| Load and Display a React List with useEffect | Preserve stable keys after moving items. |
| Build an Accessible Recursive Expand-and-Collapse Tree in React | Separate selection, focus, and keyboard commands. |
| Shopping Cart with Undo and Redo for Add and Remove Operations | Commit an operation once and restore the exact previous order. |
| Plan Testing and Deployment for a React Feature | Add cancel, touch, and stale-server tests. |
| Selection-State Class for an Infinite-Scroll Checkbox List with Select All | Keep row identity independent of visible position. |
For one task, write the pure operation before adding UI events. Then explain a canceled preview and a stale server response. Those two follow-ups test whether you own the state contract rather than only the happy-path animation.
Finish with stable state, not just movement
A drag-and-drop coding interview answer should leave one reorder command, explicit preview ownership, equivalent non-drag controls, and a recovery policy. Demonstrate that moving an item changes its position without changing its identity or erasing later work.
Extend the React list task with a stable-ID move. Demonstrate cancel and a stale rejection, then check focus and non-drag controls in the actual UI before polishing the animation.
Sources and Further Reading
- MDN: HTML Drag and Drop API
- W3C: Rearrangeable listbox example
- W3C: Dragging movements
- MDN: Pointer events
- React: Rendering lists
Research checked October 1, 2026. The reducer and interaction protocol are original exercises. Pure tests do not establish cross-browser touch behavior or assistive-technology compatibility.