Ordering Keys for Reorderable Lists: Fractional Indexing, LexoRank, and Sequence CRDTs

2026-04-12 · software

Ordering Keys for Reorderable Lists: Fractional Indexing, LexoRank, and Sequence CRDTs

Date: 2026-04-12
Category: knowledge
Domain: software / data modeling / collaboration / editors / kanban / realtime systems

1) Why this topic matters more than it looks

A surprising number of products eventually need user-defined ordering:

The naive version looks trivial:

Then real life arrives:

This is one of those “small schema choice, large system consequences” problems.


2) The short version

If you only need the decision fast:

Pocket rule:

If you care mostly about cheap reordering, use fractional indexing.
If you care mostly about concurrent textual adjacency, use sequence CRDTs.
If you care mostly about operational backlog ranking at scale, LexoRank-style schemes are worth it.


3) What you are actually optimizing

Before picking a scheme, answer these five questions:

  1. How many rows may need rewriting per move?
  2. Can multiple users reorder concurrently?
  3. Must sequential inserts from the same user stay contiguous under concurrency?
  4. Will clients work offline / merge later?
  5. Do operators need repair / rebalance tooling for pathological cases?

Most ordering systems are trading among five things:

There is no universal winner.


4) Option A: dense integers

The model

Store positions as integers:

To insert in the middle, rewrite all subsequent rows.

Why people start here

Why it breaks down

Move one item near the top of a long list and you may need to rewrite a large suffix.

That means:

For frequently reordered lists, this is the wrong default.

When it is still fine


5) Option B: integers with gaps

The model

Start with spaced positions instead of dense ones:

Insert between 100 and 200 by assigning 150.

This reduces rewrites for a while.

Why it helps

Why it is temporary, not permanent

Eventually hotspots form. If users keep inserting between the same neighbors, you run out of room and need a rebalance.

Steve Dignam’s practical comparison makes the core point clearly: gap schemes reduce blast radius, but do not eliminate the need for occasional re-spacing.

Best fit

Bad fit


6) Option C: floating-point / decimal fractional positions

The model

Store a position between neighbors by averaging:

This is the first form of fractional indexing most people discover.

Why it is attractive

The problem: finite precision is real

With IEEE-754 doubles, repeated midpoint insertion eventually collapses.

You do not have infinite distinct values between two floats in practice.

Steve Ruiz shows the practical failure mode nicely: after enough repeated splits between two nearby values, the next “between” result stops being strictly between.

That means your ordering invariant can silently die.

Arbitrary precision decimals help, but...

Arbitrary precision numeric/decimal types extend the runway a lot. They are much better than plain doubles. But they are still fundamentally a precision budget system, not an actually unbounded identifier scheme.

For many apps that is enough. For long-lived heavy-reordering systems, it is usually not what I would choose.

Recommendation


7) Option D: string-based fractional indexing

The model

Instead of numbers, use sortable strings that can always generate a new key between two existing keys.

Figma’s write-up is the clean practical reference here:

In Figma’s implementation, order keys are stored as strings and midpoint generation is done with string operations, avoiding float precision exhaustion.

Why this is such a strong default

For server-authoritative ordered objects such as:

string fractional indexing gets you most of what you want:

Important caveat #1: key length grows

The cost you pay is identifier growth. If users keep inserting in the same tight gap, the generated strings can get longer.

In many real apps this is acceptable. But it is not “free forever.”

Operationally, you should expect one of these strategies:

Important caveat #2: concurrent runs may interleave

Figma explicitly notes the trade-off:

That is the key boundary.

If two users concurrently insert runs at the same location, simple fractional ordering may converge to a stable order that interleaves those runs. For design objects this is often fine. For paragraphs or text, it is ugly.

Important caveat #3: tie-breaking still matters

If two clients generate the same position between the same neighbors, you still need a deterministic uniqueness or server tie-break rule.

Figma’s article describes server-side tie handling for identical positions. Do not skip this in a multi-client system.

Best fit

Bad fit


8) Option E: LexoRank-style schemes

The model

LexoRank is Atlassian’s ranking system used for Jira issue ordering. At a high level, it is a string rank approach designed for scalable sortable issue prioritization, along with maintenance and integrity tooling.

Even without depending on every internal detail, Atlassian’s documentation reveals the operational flavor:

That tells you something important:

LexoRank is not just an algorithm. It is an algorithm plus an operational maintenance model.

Why people use it

What makes it different from generic fractional indexing

Generic fractional indexing says:

LexoRank-style systems say:

That is a meaningful difference in enterprise systems.

Trade-offs

Pros:

Cons:

Best fit

My practical view

If you are not building “Jira-like ranked backlog at scale,” you probably do not need full LexoRank complexity. Basic string fractional indexing is often enough.

But if ranking is a core product primitive with years of operational life, LexoRank-style thinking is worth studying.


9) Option F: sequence CRDTs / tree-based indexing

The model

When collaboration is not just “many users talking to one server” but closer to:

then the problem changes.

Now you are not just assigning sortable ranks. You are designing a merge semantics for ordered sequences.

This is where sequence CRDT families show up:

Why simple fractional indexing stops being enough

Bartosz Sypytkowski’s write-up on non-interleaving LSeq explains the classic pain point:

That is “technically correct, user-visible nonsense.”

For text or paragraphs, adjacency is part of meaning. Interleaving destroys intent.

Evan Wallace’s tree-based indexing insight

Evan Wallace presents a useful middle ground for ordered objects under collaborative concurrency:

The key benefit is subtle but big:

Concurrent runs inserted in the same place stay as runs, instead of becoming alternating soup.

That makes tree-based or adjacency-aware CRDT strategies appropriate for:

The cost

You do not get this for free. Common costs include:

Best fit

Bad fit


10) Decision table

Use dense integers when

Use gapped integers when

Use string fractional indexing when

Use LexoRank-style ranking when

Use sequence CRDT / tree-based indexing when


11) Common mistakes

Mistake 1: using plain floats in production because the demo looked fine

This is one of the most common traps. The first week is smooth. Then a hotspot list gets hammered, and precision edge cases quietly arrive.

Mistake 2: over-engineering with CRDTs for a simple server-backed kanban board

If the server is authoritative and clients are online, CRDT complexity is often wasted. A good fractional index is easier to build and easier to operate.

Mistake 3: using simple fractional ordering for text blocks and discovering interleaving later

If adjacency matters, test concurrency early. A stable total order is not enough. You care about preserving runs, not just item uniqueness.

Mistake 4: ignoring the operator story

Any long-lived ranking system needs answers for:

Jira’s LexoRank docs are a nice reminder that ranking systems eventually become operational systems.

Mistake 5: confusing “sortable key” with “semantic merge model”

A sortable rank solves ordering. It does not automatically solve collaborative intent preservation.

That distinction matters a lot.


12) Practical recommendations by product type

Kanban / issue tracker

Default pick:

Why:

Design tool layers / whiteboard z-order

Default pick:

Why:

This is exactly the type of use case Figma’s approach is excellent for.

CMS blocks / outline sections with ordinary collaboration

Default pick:

Rich-text / paragraph collaborative editor

Default pick:

Why:

Small internal admin app

Default pick:

Why:


13) A simple selection rule I would actually use

If I were choosing quickly:

Case A — ordinary product list ordering

I would choose string fractional indexing.

Reason:

Case B — Jira-like ranked backlog as a core business object

I would study LexoRank-style operations and maintenance from day one.

Reason:

Case C — collaborative text/block structure with real offline merge

I would choose a sequence CRDT family.

Reason:

Case D — tiny CRUD admin tool

I would choose gapped integers and move on with my life.

Reason:


14) Schema and ops notes

For fractional indexing

Store at least:

Operationally:

For LexoRank-style systems

Also plan for:

For sequence CRDTs

Also plan for:


15) TL;DR

The core distinction is this:

Ordering keys solve cheap reordering.
Sequence CRDTs solve collaborative intent under concurrency.

Choose based on which problem you actually have.


References / starting points