Fundamentals 11 min read

Mastering Skip Lists in Go: A Simple Probabilistic Alternative to Balanced Trees

This article explains the principles of skip lists, compares them with balanced trees, and provides a complete Go implementation—including interface design, random level generation, search, insertion, deletion, and iteration—while highlighting their space efficiency and ease of use.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
Mastering Skip Lists in Go: A Simple Probabilistic Alternative to Balanced Trees

Skip lists are a probabilistic data structure that can replace balanced trees; Redis's sorted set (zset) and LevelDB use them. This article introduces the basic principle and provides a simple Go implementation.

Skip lists use multiple forward pointers per node, achieving O(log n) average complexity while being simpler and more space‑efficient than balanced trees.

Advantages of skip lists include easier insertion and deletion, simpler implementation, lower memory usage, and better extensibility.

Insertion and deletion are simpler than in balanced trees

Implementation is straightforward

More space‑efficient

Easier to extend

Interface Design

The skip list supports float64 keys and values of any type, with operations Search, Insert, Delete, Len, Front, and Next.

package skiplist_test

import (
    "fmt"
    "github.com/protream/go-datastructures/skiplist"
)

func ExampleSkipList() {
    sl := skiplist.New()

    sl.Insert(float64(100), "foo")

    e, ok := sl.Search(float64(100))
    fmt.Println(ok)
    fmt.Println(e.Value)
    e, ok = sl.Search(float64(200))
    fmt.Println(ok)
    fmt.Println(e)

    sl.Insert(float64(20.5), "bar")
    sl.Insert(float64(50), "spam")
    sl.Insert(float64(20), 42)

    fmt.Println(sl.Len())
    e = sl.Delete(float64(50))
    fmt.Println(e.Value)
    fmt.Println(sl.Len())

    for e := sl.Front(); e != nil; e = e.Next() {
        fmt.Println(e.Value)
    }
    // Output:
    // true
    // foo
    // false
    // <nil>
    // 4
    // spam
    // 3
    // 42
    // bar
    // foo
}

Definitions

Constants maxLevel and probability p control the maximum height and level distribution.

const (
    maxLevel int   = 16 // Should be enough for 2^16 elements
    p        float32 = 0.25
)

Node definition:

// Element is an element of a skiplist.
type Element struct {
    Score   float64
    Value   interface{}
    forward []*Element
}

func newElement(score float64, value interface{}, level int) *Element {
    return &Element{
        Score:   score,
        Value:   value,
        forward: make([]*Element, level),
    }
}

SkipList struct and constructor:

// SkipList represents a skiplist.
// The zero value of SkipList is an empty skiplist ready to use.
type SkipList struct {
    header *Element // header is a dummy element
    len    int      // current skiplist length, header not included
    level  int      // current skiplist level, header not included
}

// New returns a new empty SkipList.
func New() *SkipList {
    return &SkipList{
        header: &Element{forward: make([]*Element, maxLevel)},
    }
}

Random Level

func randomLevel() int {
    level := 1
    for rand.Float32() < p && level < maxLevel {
        level++
    }
    return level
}

Iteration

Front and Next methods enable sequential traversal of the skip list.

// Front returns first element in the skiplist which may be nil.
func (sl *SkipList) Front() *Element {
    return sl.header.forward[0]
}

// Next returns first element after e.
func (e *Element) Next() *Element {
    if e != nil {
        return e.forward[0]
    }
    return nil
}

Search

Search proceeds from the highest level down, locating the predecessor at each level.

// Search the skiplist to find element with the given score.
// Returns (*Element, true) if the given score is present, otherwise returns (nil, false).
func (sl *SkipList) Search(score float64) (element *Element, ok bool) {
    x := sl.header
    for i := sl.level - 1; i >= 0; i-- {
        for x.forward[i] != nil && x.forward[i].Score < score {
            x = x.forward[i]
        }
    }
    x = x.forward[0]
    if x != nil && x.Score == score {
        return x, true
    }
    return nil, false
}

Insert

Insertion locates update points for each level, creates a new element, and adjusts pointers.

// Insert (score, value) pair to the skiplist and returns pointer of element.
func (sl *SkipList) Insert(score float64, value interface{}) *Element {
    update := make([]*Element, maxLevel)
    x := sl.header
    for i := sl.level - 1; i >= 0; i-- {
        for x.forward[i] != nil && x.forward[i].Score < score {
            x = x.forward[i]
        }
        update[i] = x
    }
    x = x.forward[0]

    // Score already present, replace value.
    if x != nil && x.Score == score {
        x.Value = value
        return x
    }

    level := randomLevel()
    if level > sl.level {
        level = sl.level + 1
        update[sl.level] = sl.header
        sl.level = level
    }
    e := newElement(score, value, level)
    for i := 0; i < level; i++ {
        e.forward[i] = update[i].forward[i]
        update[i].forward[i] = e
    }
    sl.len++
    return e
}

Delete

Deletion is similar to insertion, updating pointers and decreasing the length.

// Delete remove and return element with given score, return nil if element not present
func (sl *SkipList) Delete(score float64) *Element {
    update := make([]*Element, maxLevel)
    x := sl.header
    for i := sl.level - 1; i >= 0; i-- {
        for x.forward[i] != nil && x.forward[i].Score < score {
            x = x.forward[i]
        }
        update[i] = x
    }
    x = x.forward[0]

    if x != nil && x.Score == score {
        for i := 0; i < sl.level; i++ {
            if update[i].forward[i] != x {
                return nil
            }
            update[i].forward[i] = x.forward[i]
        }
        sl.len--
    }
    return x
}

Conclusion

This article presents a straightforward Go implementation of a skip list, noting that concurrency safety can be added with sync.RWLock. For a deeper dive, refer to the original paper "Skip Lists: A Probabilistic Alternative to Balanced Trees".

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Data StructuresSearchSkip ListDeletionInsertionprobabilistic algorithm
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.