Build a Drag & Drop
Two approaches: the native HTML Drag and Drop API (draggable, onDragStart/Over/Drop) or pointer-events math. State tracks the dragged item and drop target; reorder the list immutably on drop. Mention accessibility (keyboard reordering) and that a library (dnd-kit) is the production choice.
Drag & drop has two implementation paths — know both, and know that production usually means a library.
Approach 1: native HTML Drag and Drop API
function DragList({ items, setItems }) {
const dragItem = useRef(null); // index being dragged
const dragOverItem = useRef(null); // index being hovered
const handleDrop = () => {
const list = [...items];
const [moved] = list.splice(dragItem.current, 1); // remove dragged
list.splice(dragOverItem.current, 0, moved); // insert at target
setItems(list);
dragItem.current = dragOverItem.current = null;
};
return items.map((item, i) => (
<div
key={item.id}
draggable
onDragStart={() => (dragItem.current = i)}
onDragEnter={() => (dragOverItem.current = i)}
onDragOver={(e) => e.preventDefault()} // REQUIRED to allow a drop
onDragEnd={handleDrop}
>
{item.label}
</div>
));
}Key gotchas: you must e.preventDefault() on onDragOver or onDrop never fires; data can be carried via dataTransfer; the native drag image and styling are limited.
Approach 2: pointer events
Listen to pointerdown/move/up, track the pointer position, and compute which slot the dragged element is over by comparing bounding rects. More code, but full control — custom drag previews, smooth animations, touch support, constraints. This is what real libraries do under the hood.
State model
- The dragged item (id/index) and the current drop target.
- On drop, reorder the list immutably (splice on a copy) — never mutate state directly.
- Use stable item ids as keys, not indices — reordering with index keys corrupts the UI.
What separates a strong answer
- Accessibility — native DnD is not keyboard-accessible. A complete solution adds keyboard reordering (focus an item, arrow keys to move, space to drop) and
aria-liveannouncements. This is the most-missed requirement. - Touch support — the native API is poor on touch; pointer events handle it properly.
- Visual feedback — drop-indicator line, placeholder gap, drag preview.
- Use a library in production — dnd-kit (modern, accessible, performant) or react-beautiful-dnd. Hand-rolling is fine for an interview to show understanding, but say you'd reach for dnd-kit for real.
- Performance — for long lists, avoid re-rendering every item on each
dragOver; throttle and memoize rows.
The framing
"Two paths: the native HTML Drag and Drop API — draggable, onDragStart/Enter/Over/Drop, with the classic gotcha that you must preventDefault on dragOver — or pointer events with bounding-rect math for full control over previews, touch, and animation. Either way, state is the dragged item plus the drop target, and on drop I reorder the list immutably with stable id keys. The senior points: native DnD isn't keyboard-accessible so I'd add keyboard reordering and aria-live, and for production I'd use dnd-kit rather than hand-roll it."
Follow-up questions
- •Why must you call preventDefault on onDragOver?
- •How would you make drag & drop keyboard-accessible?
- •Native Drag and Drop API vs pointer events — trade-offs?
- •How do you keep a long draggable list performant?
Common mistakes
- •Forgetting e.preventDefault() on onDragOver — onDrop never fires.
- •Using array indices as keys, corrupting the list on reorder.
- •Mutating the array instead of reordering immutably.
- •Ignoring accessibility — native DnD has no keyboard support.
- •Ignoring touch devices, where the native API is unreliable.
Performance considerations
- •dragOver/pointermove fire rapidly — throttle handlers and memoize list rows so the whole list doesn't re-render per event. For very long lists, combine with virtualization.
Edge cases
- •Dropping an item onto itself or outside any target.
- •Reordering to the first or last position.
- •Touch devices and the native API's poor support there.
- •Long lists re-rendering on every dragOver event.
- •Nested drop zones.
Real-world examples
- •Reorderable task lists (Trello/Jira boards), file upload zones, kanban columns.
- •dnd-kit powering accessible drag-and-drop in production apps.