Modern Software Tools, Explained Back to Explainers
Interactive explainer

The Casino Data Structure — Skip Lists

Skip lists gamble on structure in a very controlled way: every node climbs to higher levels according to coin flips, and the resulting towers behave like a balanced search structure with high probability. That sounds suspicious until you see why it works, why it is easy to augment with ranks, and why Redis uses it in the skiplist-backed form of its sorted sets.

Probabilistic balancing Redis sorted sets Rank queries via spans JavaScript implementation
§01

Why Redis needs more than a tree story

Redis sorted sets are not just “store items in order.” They need several operations at once: look up a member directly, update its score, fetch ranges by score, fetch ranges by rank, and return the rank of a member quickly. That is a harder job than plain dictionary lookup.

A hash table is great for member -> score, but it does not know anything about order. A balanced tree can represent order, but Redis does not just need ordering. It also needs ranks, range scans, and a structure that is straightforward to augment and maintain.

In Redis's skiplist-backed sorted-set encoding, the system keeps two views of the same data: a hash table for direct member lookup and a skip list for score order. Small sorted sets can stay in a more compact representation until they outgrow it.
One sorted set, two access paths

Hash Table

Member → score

Perfect for exact lookup and updates when you already know the member name.

Skip List

Score → ordered walk

Perfect for rank, range, predecessor/successor, and walking the set in sorted order.

Together

The Redis move

When Redis switches to its skiplist-backed encoding, it uses the hash table for identity and the skip list for order, so neither structure has to be contorted into doing both jobs alone.

§02

Probabilistic balancing

A skip list is a stack of ordered linked lists. The bottom level contains every element. Higher levels contain progressively fewer elements, acting as express lanes. To decide how tall a node becomes, you repeatedly flip a coin: promote once for heads, stop on tails.

Search works by starting at the top-left of the structure. At each level, you move right while the next key is still less than your target. When moving right would overshoot, you drop down one level and continue. By the time you reach level 0, you are either sitting on the target key or on the last key before where it should be. The higher levels let you skip across big stretches of the list quickly; the lower levels do the fine-grained cleanup.

Insertion uses almost the same walk. As you search for the insertion point, you remember the last node visited at each level. Those remembered nodes are the predecessors. Then you flip coins to choose the new node's height and splice the new tower in after those predecessors on each level it occupies. So insertion is not a global rebalance. It is a local pointer rewrite after one top-down search.

No explicit rotations. No recoloring. No global rebalancing pass. The structure stays well-shaped with high probability because the number of tall towers falls off geometrically. That is what “probabilistic balancing” means here: the balancing policy is encoded in the level distribution rather than by deterministic repair rules.

If the promotion probability is p = 1/2, roughly half the nodes appear on level 2, a quarter on level 3, an eighth on level 4, and so on.
Reroll the towers
Promotion p
0.25
Average height
0
Tallest tower
0
Express lanes
0
Higher p = more tall towersExpected height is low
random_levels.js — tower heights from repeated coin flips

      
§03

Why skip lists play well with concurrency

Balanced trees can absolutely be made concurrent. The issue is not possibility; it is engineering friction. Tree updates often involve rotations and multi-node structural cases. Getting all of those corner cases correct under interleaving updates is delicate.

Skip lists mutate more locally. Insertion and deletion are mostly pointer splices across a small number of levels. There is no rotation logic, and the invariants are easier to state: each level is ordered, and each node's tower is vertically aligned. That simplicity is why skip lists show up so often in lock-free and concurrent data-structure literature.

William Pugh's concurrent skip-list paper makes the key point directly: skip lists preserve much of the simplicity of unbalanced trees while still giving good expected performance.
Mutation complexity at a glance

Red-Black Tree

Skip List

Why it matters

§04

Redis sorted sets in practice

In Redis's skiplist-backed sorted-set encoding, the implementation comment says the structure uses two data structures to hold the same elements: a hash table mapping elements to scores, and a skip list mapping scores to elements. That gives O(log N) ordered updates while preserving direct member lookup. Small sorted sets may instead use a compact listpack encoding.

The interesting extra is span metadata. Each forward pointer stores how many bottom-level nodes it skips. That lets Redis answer rank queries in logarithmic time by accumulating spans while descending the structure. Redis also keeps a backward pointer at level 1, which makes reverse range traversal efficient.

A tiny Redis-like sorted set

Hash table view

Skip list structure

Top rows are sparse express lanes. The bottom row contains every member in score order, which is where rank and range queries are counted.
Query member
alice
Score
0
Rank
0
Range 2..4
redis_like_skiplist.js — spans and rank queries in a simplified skip list

      
§05

Skip list in JavaScript

The implementation below is a generic ordered skip list in JavaScript. It stores a value at each key, maintains span metadata so ranks can be answered in logarithmic expected time, and includes the core ordered-map operations: insertion, lookup, deletion, rank, and selection by rank.

This is the same structural idea Redis uses in its skiplist-backed sorted sets, generalized into a reusable skip list map you can run directly in the page.
skip_map.js — generic ordered skip list with spans, rank, and deletion