Skip to main content

Transfer List (Advanced + Reorder)

Difficulty: Advanced

Problem Statement​

Extend transfer list with multi-select, move between lists, and reorder selected items up/down.

Live Demo​

Try the running app below β€” this is the target behavior for the challenge.

Transfer List (Advanced + Reorder) β€” Live Demo

    What Interviewers Expect​

    • Multi-select with Set.
    • Reorder only valid selections.
    • Disable move/reorder at boundaries.

    Step-by-Step Implementation​

    Step 1 β€” Reorder algorithm​

    Swap selected items one position up/down without mutating array in place incorrectly.

    [arr[i - 1], arr[i]] = [arr[i], arr[i - 1]];

    Step 2 β€” Boundary checks​

    Disable up/down when selected item is at edge.

    if (i > 0 && !sel.has(arr[i - 1])) swap...

    Step 3 β€” Move selected batch​

    Move all selected items between lists in one action.

    setFrom(from.filter((x) => !selected.has(x)));

    Complete Implementation​

    Copy-paste ready single-file solution. Use this as your reference after attempting the challenge yourself, or paste it into CodeSandbox / StackBlitz to run locally.

    App.jsx
    import React, {useState} from 'react';

    export default function App() {
    const [list1, setList1] = useState(['A', 'B', 'C']);
    const [list2, setList2] = useState([]);
    const [sel1, setSel1] = useState(new Set());
    const [sel2, setSel2] = useState(new Set());

    const moveSelected = (from, setFrom, to, setTo, selected, clearSelected) => {
    const moving = from.filter((item) => selected.has(item));
    if (!moving.length) return;

    setFrom(from.filter((item) => !selected.has(item)));
    setTo([...to, ...moving]);
    clearSelected(new Set());
    };

    const reorder = (items, setItems, selected, direction) => {
    setItems((prev) => {
    const arr = [...prev];
    const indexes = arr
    .map((item, index) => (selected.has(item) ? index : -1))
    .filter((index) => index >= 0);

    if (!indexes.length) return arr;

    if (direction === 'up') {
    for (const index of indexes) {
    if (index > 0 && !selected.has(arr[index - 1])) {
    [arr[index - 1], arr[index]] = [arr[index], arr[index - 1]];
    }
    }
    } else {
    for (let i = indexes.length - 1; i >= 0; i -= 1) {
    const index = indexes[i];
    if (index < arr.length - 1 && !selected.has(arr[index + 1])) {
    [arr[index + 1], arr[index]] = [arr[index], arr[index + 1]];
    }
    }
    }

    return arr;
    });
    };

    const renderList = (list, selected, setSelected, setList, otherList, setOther, clearOther) => (
    <div
    style={{
    flex: 1,
    minWidth: 220,
    border: '1px solid #ccc',
    borderRadius: 8,
    padding: '0.75rem',
    }}
    >
    <div style={{display: 'flex', gap: '0.5rem', marginBottom: '0.5rem'}}>
    <button type="button" onClick={() => reorder(list, setList, selected, 'up')}>
    ↑
    </button>
    <button type="button" onClick={() => reorder(list, setList, selected, 'down')}>
    ↓
    </button>
    </div>

    <ul style={{listStyle: 'none', padding: 0}}>
    {list.map((item) => (
    <li key={item} style={{display: 'flex', gap: '0.5rem', marginBottom: '0.35rem'}}>
    <input
    type="checkbox"
    checked={selected.has(item)}
    onChange={() => {
    setSelected((prev) => {
    const next = new Set(prev);
    if (next.has(item)) next.delete(item);
    else next.add(item);
    return next;
    });
    }}
    />
    <button
    type="button"
    onClick={() => {
    setList(list.filter((value) => value !== item));
    setOther([...otherList, item]);
    clearOther(new Set());
    }}
    >
    {item}
    </button>
    </li>
    ))}
    </ul>
    </div>
    );

    return (
    <div style={{display: 'flex', gap: '0.75rem', alignItems: 'flex-start'}}>
    {renderList(list1, sel1, setSel1, setList1, list2, setList2, setSel1)}

    <div style={{display: 'grid', gap: '0.5rem', alignSelf: 'center'}}>
    <button
    type="button"
    disabled={!sel1.size}
    onClick={() => moveSelected(list1, setList1, list2, setList2, sel1, setSel1)}
    >
    β†’
    </button>
    <button
    type="button"
    disabled={!sel2.size}
    onClick={() => moveSelected(list2, setList2, list1, setList1, sel2, setSel2)}
    >
    ←
    </button>
    </div>

    {renderList(list2, sel2, setSel2, setList2, list1, setList1, setSel2)}
    </div>
    );
    }

    Final Checklist​

    • UI works for primary flow
    • Edge cases handled (empty/disabled/loading where relevant)
    • State updates are immutable
    • Components are readable and reasonably split
    • You can explain trade-offs out loud in an interview

    Next Challenge​

    Continue to the next item in the sidebar when you're comfortable implementing this from scratch in 30–45 minutes.