test data
PRANSHUL
Engineering

One Row Per Drag

Fractional indexing lets a kanban drag write exactly one row. A short appreciation of an elegant, underused trick.

By PranshulA 5-minute read

The naive kanban schema orders cards with integers: 1, 2, 3. Then a card moves and you renumber half the column inside a transaction, and your realtime subscribers all reflow, and the drag feels heavier than it should. The fix is old, elegant, and still weirdly obscure: stop numbering, start naming.

Keys with room between them§

Fractional indexing stores position as a string with lexicographic order — 'a0', 'a1', and when a card lands between them, 'a0V'. There is always another string between two strings. A move touches exactly one row, no matter how long the column is.

 typescript
import { generateKeyBetween } from "fractional-indexing";

const first = generateKeyBetween(null, null); // "a0"
const second = generateKeyBetween(first, null); // "a1"
const between = generateKeyBetween(first, second); // "a0V"

// The drop handler writes ONE row:
await db.from("tasks")
  .update({ column_id: target, position: between })
  .eq("id", taskId);

The Postgres detail that matters§

Declare the column as text COLLATE "C". Linguistic collations will helpfully reorder your keys by locale rules and quietly corrupt the sort; the C collation compares bytes, which is exactly what the algorithm assumes.

Good schema design is mostly finding the representation in which the common operation becomes trivial.

Leave a mark

Pass it on

Further reading

Letters to the editor

Correspondence