Machine-Coding: Build a Drag-and-Drop Interface

Drag-and-drop is one of the harder machine-coding rounds at frontend interviews. The candidate has 45-60 minutes to build a working drag-and-drop interface — usually a reorderable list, sometimes a kanban board. The exercise is harder than autocomplete or infinite scroll because the browser API choices matter, accessibility requires meaningful work, and the implementation has many edge cases that bite under time pressure.

This piece walks through the implementation patterns, the API tradeoffs, and the senior-level expectations.

The typical prompt

  • “Build a reorderable to-do list. The user can drag items to reorder them.”
  • “Implement a kanban board with three columns. Items can be dragged between columns.”
  • “Build a sortable image gallery.”

The HTML5 Drag and Drop API vs Pointer Events

Two main approaches:

HTML5 Drag and Drop API

Built-in browser drag events: dragstart, dragover, drop, dragend. Pros: simple to set up, browser handles drag image and cursor. Cons: limited customization, no touch support out of the box, accessibility is harder.

Pointer Events / Mouse Events

Track pointerdown, pointermove, pointerup manually. Pros: full control, works on touch and mouse uniformly, easier to make accessible. Cons: more code, you implement the drag image and cursor yourself.

Senior candidates pick pointer events for non-trivial drag-drop. The HTML5 API is fine for simple desktop-only cases but breaks down for production-quality UI.

Implementation: the pointer-event approach

Step 1: state and structure

function ReorderableList({ items, setItems }) {
  const [dragState, setDragState] = useState(null); // { fromIndex, currentY, originalY }

  function onPointerDown(e, index) {
    setDragState({
      fromIndex: index,
      originalY: e.clientY,
      currentY: e.clientY,
    });
  }

  function onPointerMove(e) {
    if (!dragState) return;
    setDragState(s => ({ ...s, currentY: e.clientY }));
  }

  function onPointerUp() {
    if (!dragState) return;
    const targetIndex = computeTargetIndex(dragState);
    if (targetIndex !== dragState.fromIndex) {
      reorder(dragState.fromIndex, targetIndex);
    }
    setDragState(null);
  }

  return (
    <ul onPointerMove={onPointerMove} onPointerUp={onPointerUp}>
      {items.map((item, i) => (
        <li
          key={item.id}
          onPointerDown={e => onPointerDown(e, i)}
          style={{ transform: getTransform(i, dragState) }}
        >
          {item.label}
        </li>
      ))}
    </ul>
  );
}

Step 2: visual feedback during drag

While dragging, the dragged item should follow the pointer. Other items should shift to indicate where the drop will land.

function getTransform(index, dragState) {
  if (!dragState) return 'none';
  if (index === dragState.fromIndex) {
    // Move with pointer
    return `translateY(${dragState.currentY - dragState.originalY}px)`;
  }
  // Other items shift if dragged item is over them
  const targetIndex = computeTargetIndex(dragState);
  if (dragState.fromIndex < targetIndex && index > dragState.fromIndex && index <= targetIndex) {
    return 'translateY(-100%)'; // shift up
  }
  if (dragState.fromIndex > targetIndex && index < dragState.fromIndex && index >= targetIndex) {
    return 'translateY(100%)'; // shift down
  }
  return 'none';
}

Step 3: drop and reorder

function reorder(fromIndex, toIndex) {
  setItems(items => {
    const next = [...items];
    const [moved] = next.splice(fromIndex, 1);
    next.splice(toIndex, 0, moved);
    return next;
  });
}

Step 4: accessibility

Mouse-only drag-drop fails accessibility. Senior+ rounds expect keyboard-accessible reordering. The pattern:

  • Each item has tabindex=0 to be keyboard-focusable.
  • Pressing Space toggles “selected for reorder” mode.
  • Arrow keys move the selected item up or down.
  • Pressing Space or Enter again confirms the new position.
  • An ARIA live region announces the changes for screen readers.
<li
  tabIndex={0}
  role="option"
  aria-selected={selectedForReorder === i}
  aria-grabbed={selectedForReorder === i}
  onKeyDown={e => handleKeyDown(e, i)}
>{item.label}</li>

The ARIA grabbed/dropeffect attributes are part of the older accessibility pattern; modern recommendations also use aria-roledescription=”draggable” with live-region announcements.

Common pitfalls

  • Using HTML5 drag API for non-trivial UIs. Touch support is broken; customization is limited.
  • Not handling pointer leave or cancel. If the user releases outside the list, drag state can get stuck.
  • No accessibility. Drag-drop without keyboard support fails the senior+ bar.
  • Layout shift bugs. When items shift to make room for the dragged one, layout calculations can race with the pointer move.
  • No drop zones. For kanban boards, the column itself must be a valid drop target; falling through to the items is wrong.
  • Performance issues with many items. Re-rendering every list item on every pointer move is expensive; transform-based positioning is much faster.

Stretch goals

  • Multi-select drag (drag several items at once).
  • Drag between containers (kanban board).
  • Auto-scroll when dragging near container edges.
  • Drag preview that follows the cursor.
  • Animated reordering (FLIP technique).

The library option

In production, most drag-drop is built with libraries: dnd-kit (modern, accessible), react-dnd (older, HTML5 based), Framer Motion’s drag (animation-friendly). For the interview, you should know these exist and articulate why you’d use one in production. But the round itself usually wants you to build from scratch.

Time budget for a 45-minute round

  • 0-5 min: clarify requirements, ask about touch support and accessibility expectations.
  • 5-20 min: basic pointer-event-based drag working.
  • 20-30 min: visual feedback (shifted items), drop logic.
  • 30-40 min: accessibility (keyboard support, ARIA).
  • 40-45 min: edge cases (cancel, leave), stretch goals.

Frequently Asked Questions

Should I use HTML5 drag-drop or pointer events?

Pointer events for non-trivial UIs. HTML5 is fine for the simplest reorder cases but degrades fast.

Is keyboard support always required?

For senior+ rounds, generally yes. For junior rounds, the bar is sometimes lower but it scores well to add it.

How do I handle touch support?

Pointer events handle both mouse and touch uniformly. If you use HTML5 drag-drop, you need a separate touch implementation.

What’s the FLIP technique?

First, Last, Invert, Play — a technique for animating layout changes by capturing positions before and after, then animating from old to new. Useful for animated reordering. Senior+ stretch goal.

Are drag-drop rounds common at FAANG?

Common at frontend-specialist roles, less common at generalist SWE roles. Modern frontend-heavy companies (Linear, Notion, Figma) often include them.

Scroll to Top