Google - Design Skiplist

1. Requirements

  • insert

  • search

  • erase:

2. Thinking Process

Level 3:    1 ----------- 9 ----------- 20
Level 2:    1 ---- 5 ---- 9 ---- 14 ---- 20
Level 1:    1 - 3 - 5 - 7 - 9 - 12 - 14 - 17 - 20
Level 0:    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

  1. Insert: Add item to level 0 first, random to update to level you want => But when add to level 2, must add to level 0 and level 1.

  2. Search: search from Level 3 -> Level 2 -> Level 1 -> Level 0.

3. Code Walk-through

import random

MAX_LEVEL = 16  # limit height for simplicity
P_FACTOR = 0.5  # probability to grow a level (like coin toss)


class Node:
    """Each node stores a value and forward pointers to next nodes at different levels."""
    def __init__(self, val, level):
        self.val = val
        self.forward = [None] * (level + 1)  # forward[i] points to next node at level i


class Skiplist:
    def __init__(self):
        self.head = Node(-float("inf"), MAX_LEVEL)  # head sentinel node
        self.level = 0  # current highest level in the list

    # ------------- Helper -------------
    def random_level(self):
        """Randomly decide the level of a new node (like flipping a coin)."""
        lvl = 0
        while random.random() < P_FACTOR and lvl < MAX_LEVEL:
            lvl += 1
        return lvl

    # ------------- Search -------------
    def search(self, target: int) -> bool:
        """
        Start from top level and move right until next is >= target.
        Then drop down one level. Repeat until level 0.
        """
        curr = self.head
        for i in range(self.level, -1, -1):
            while curr.forward[i] and curr.forward[i].val < target:
                curr = curr.forward[i]
        curr = curr.forward[0]
        return curr is not None and curr.val == target

    # ------------- Insert -------------
    def insert(self, num: int) -> None:
        """
        1. Record nodes at each level just before where new node will go.
        2. Randomly choose new node's height.
        3. Insert and adjust forward pointers.
        """
        update = [None] * (MAX_LEVEL + 1)
        curr = self.head

        # Step 1: move right/down to find insert position
        for i in range(self.level, -1, -1):
            while curr.forward[i] and curr.forward[i].val < num:
                curr = curr.forward[i]
            update[i] = curr

        # Step 2: choose random level for new node
        lvl = self.random_level()

        # if new node is higher than current max, link head at new levels
        if lvl > self.level:
            for i in range(self.level + 1, lvl + 1):
                update[i] = self.head
            self.level = lvl

        # Step 3: create and insert node
        new_node = Node(num, lvl)
        for i in range(lvl + 1):
            new_node.forward[i] = update[i].forward[i]
            update[i].forward[i] = new_node

    # ------------- Erase -------------
    def erase(self, num: int) -> bool:
        """
        1. Like insert, find nodes before target at each level (update[]).
        2. If found, reconnect pointers to skip target node.
        3. Adjust top level if necessary.
        """
        update = [None] * (MAX_LEVEL + 1)
        curr = self.head

        # Step 1: find predecessors
        for i in range(self.level, -1, -1):
            while curr.forward[i] and curr.forward[i].val < num:
                curr = curr.forward[i]
            update[i] = curr

        curr = curr.forward[0]
        if curr is None or curr.val != num:
            return False  # not found

        # Step 2: unlink the target node
        for i in range(self.level + 1):
            if update[i].forward[i] != curr:
                continue
            update[i].forward[i] = curr.forward[i]

        # Step 3: reduce list level if top level becomes empty
        while self.level > 0 and self.head.forward[self.level] is None:
            self.level -= 1

        return True

4. Dry run

Idea: Move right first -> Else go down, and find the exact node at the end.

  1. Start from head at top level (level 3).
  • Move right: 1 → 9 → stop (next 20 > 14).

  • Move down to level 2.

  1. At level 2:
  • From 9 → 14 ✅ found equal.

  • Move down to level 1 to confirm.

  1. At level 1:
  • From 9 → 12 → 14 ✅ match found.
  1. At level 0:
  • Move

Note: Using curr to detect and move => If you find 14 in level 2, 14 != 14 => Keep the curr and move forward to the next level

=> Also you can implement to return 14 if find in the level

Why we do not return 14 in high level when we found but need to travel until level 0 ?

  • “Finding” at a higher level isn’t guaranteed correct position

4.2. Insert

Idea: Use updated array to find the curr value that we need to modify in each level.

Level 2:  1 ---- 5 ---- 9 ---- 18 ---- 20
Level 1:  1 - 3 - 5 - 7 - 9 - 12 - 17 - 18 - 20
Level 0:  1 2 3 4 5 6 7 8 9 10 11 12 13 17 18 19 20
| Level | Traverse path    | Stop at node | Reason         | update[i]      |
| ----- | ---------------- | ------------ | -------------- | -------------- |
| 2     | head  1  5  9 | 9            | next (18) > 14 | update[2] = 9  |
| 1     | start at 9  12  | 12           | next (17) > 14 | update[1] = 12 |
| 0     | start at 12  13 | 13           | next (17) > 14 | update[0] = 13 |

4.3. Delete

Idea: Use updated array to find the curr value that we need to modify in each level.

| Level | Traverse path    | Stop at node | Reason         | update[i]      |
| ----- | ---------------- | ------------ | -------------- | -------------- |
| 2     | head  1  5  9 | 9            | next (18) > 14 | update[2] = 9  |
| 1     | start at 9  12  | 12           | next (17) > 14 | update[1] = 12 |
| 0     | start at 12  13 | 13           | next (17) > 14 | update[0] = 13 |

5. Complexity

Operation	Average Time	Worst Time	Space
Search	O(log n)	O(n)	O(n)
Insert	O(log n)	O(n)	O(n)
Erase	O(log n)	O(n)	O(n)
Last Updated On October 24, 2025