Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Coding Interview Patterns  Module 29 – Design-a-data-structure problems

Insert, delete and getRandom in O(1)

A set with insert, delete and getRandom in O(1) on average: an original raffle-box problem, its constraints, its examples, the brute force and its signal.

  • Intermediate
  • 35 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free intro, Pro: 8 of 35 minutes free
Free: The problem: a raffle box · The brute force, and why it fails at these sizes. With a pass: the other 12 sections and 3 interview answers, about 27 minutes.

What you will learn

  • Combine an array with a hash map of positions for O(1) insert, delete and random picks
  • Delete by moving the last item into the hole, in the order that survives deleting the last item
  • Test random picks without randomness, then with a seeded chi-square check
  • Explain the modulo bias of rand() % n and choose an unbiased draw in five languages
  • Support repeated items with a set of positions for each value

Before you start

What you’ll learn · Before you start

What you will learn

  • Combine an array with a hash map of positions for O(1) insert, delete and random picks
  • Delete by moving the last item into the hole, in the order that survives deleting the last item
  • Test random picks without randomness, then with a seeded chi-square check
  • Explain the modulo bias of rand() % n and choose an unbiased draw in five languages
  • Support repeated items with a set of positions for each value

Before you start

On this page

Insert, delete and getRandom in O(1) asks for a set that adds an item, deletes an item and returns an item chosen uniformly at random, each in O(1) time on average. A hash set can insert and delete in O(1) but has no positions to pick from. An array gives getRandom in O(1) through a random position, but it cannot delete from its middle without shifting. This lesson states an original version, a raffle box, with its brute force and the signal that leads to the design.

The problem: a raffle box

The problem

At a school fair, families buy numbered raffle tickets, some ask for a refund later, and every hour a ticket is drawn at random for a small prize; a drawn ticket stays in the box. Write RaffleBox. insert(t) adds ticket t and returns true, or false if t is already in the box. remove(t) takes t out and returns true, or false if it is not there. get_random() returns a ticket of the box, each with the same chance, and is never called on an empty box. So that tests can check the draws, size() returns the number of tickets, and as r runs from 0 to size() − 1, pick(r) returns every ticket exactly once; get_random() is pick of a uniformly random r. Tickets are numbers from 1 to 10⁹, a test makes up to 2 × 10⁵ calls, and each call must be O(1) on average.

  1. RaffleBox(), insert(501), insert(502), insert(501), remove(503), insert(503), remove(501), size() gives [null, true, true, false, false, true, true, 2]. Then pick(0) and pick(1) are 502 and 503 in some order, and get_random() returns each of them with a chance of one half.
  2. RaffleBox(), insert(7), remove(7), remove(7), insert(7), size() gives [null, true, true, false, true, 1]: a ticket can leave and come back.

The brute force, and why it fails at these sizes

Each obvious box has one slow call. A list gives pick(r) in O(1), but insert must search the list for a copy, and remove must find the ticket and shift every ticket after the hole. A set makes insert and remove O(1) on average, but a set has no positions, so pick(r) walks through r of its tickets.

A list box and a set box, with the work of every call counted Python · brute/brute_force.py
"""Two obvious raffle boxes, each with one slow call, and the work each call does counted.

ListBox keeps the tickets in a list: a draw reads one position, but insert and remove search the list, and a removal
shifts every ticket after the hole. SetBox keeps them in a set: insert and remove are O(1) on average, but a set has
no positions, so pick(r) walks through r of its tickets."""
import json


class ListBox:
    def __init__(self):
        self.tickets, self.work = [], 0

    def insert(self, t):
        self.work += len(self.tickets)  # the search for t
        if t in self.tickets:
            return False
        self.tickets.append(t)
        return True

    def remove(self, t):
        self.work += len(self.tickets)  # the search, then the shift of everything after the hole
        if t not in self.tickets:
            return False
        self.tickets.remove(t)
        return True

    def size(self):
        return len(self.tickets)

    def pick(self, r):
        self.work += 1
        return self.tickets[r]


class SetBox:
    def __init__(self):
        self.tickets, self.work = set(), 0

    def insert(self, t):
        self.work += 1
        if t in self.tickets:
            return False
        self.tickets.add(t)
        return True

    def remove(self, t):
        self.work += 1
        if t not in self.tickets:
            return False
        self.tickets.remove(t)
        return True

    def size(self):
        return len(self.tickets)

    def pick(self, r):
        for i, t in enumerate(self.tickets):  # no positions: walk to the r-th ticket of the iteration
            self.work += 1
            if i == r:
                return t


def replay(box, ops, args):
    return [None] + [getattr(box, op)(*a) for op, a in zip(ops[1:], args[1:])]


def fair_day(box_class, n):
    """n tickets sold, then 2n calls: a refund and a new sale (the refunds hit the oldest tickets), and a draw."""
    box = box_class()
    for t in range(1, n + 1):
        box.insert(t)
    start = box.work
    for i in range(n // 2):
        box.remove(i + 1)
        box.insert(n + 1 + i)
        box.pick(box.size() - 1 - i % box.size())
        box.pick(i % box.size())
    return box.work - start


if __name__ == "__main__":
    ops = ["RaffleBox", "insert", "insert", "insert", "remove", "insert", "remove", "size"]
    args = [[], [501], [502], [501], [503], [503], [501], []]
    for box_class in (ListBox, SetBox):
        box = box_class()
        answers = replay(box, ops, args)
        print(f"{box_class.__name__}: {json.dumps(answers)}, tickets {sorted(box.pick(r) for r in range(box.size()))}")
    print()
    print(f"{'tickets':>8} {'calls':>7} {'ListBox work':>13} {'per call':>9} {'SetBox work':>12} {'per call':>9}")
    for n in (500, 1_000, 2_000, 4_000):
        lw, sw = fair_day(ListBox, n), fair_day(SetBox, n)
        print(f"{n:>8,} {2 * n:>7,} {lw:>13,} {lw / (2 * n):>9,.0f} {sw:>12,} {sw / (2 * n):>9,.0f}")

Output

ListBox: [null, true, true, false, false, true, true, 2], tickets [502, 503]
SetBox: [null, true, true, false, false, true, true, 2], tickets [502, 503]

 tickets   calls  ListBox work  per call  SetBox work  per call
     500   1,000       250,250       250      125,750       126
   1,000   2,000     1,000,500       500      501,500       251
   2,000   4,000     4,001,000     1,000    2,003,000       501
   4,000   8,000    16,002,000     2,000    8,006,000     1,001

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brute_force.py

Both answer correctly, and on this test both do work in proportion to the number of tickets on every call: about 10¹⁰ steps at 10⁵ tickets and 2 × 10⁵ calls. With the planning figure of the constraints lesson, roughly 10⁸ steps a second for compiled code, that is well over a minute for one test.

What to notice. A uniform draw needs the tickets at positions 0 to n − 1 with no gaps, which an array gives. The array’s trouble is a hole in the middle, and nothing in the problem orders the tickets, so any ticket may fill a hole. Finding a ticket’s position is a look-up by value, which a hash map answers in O(1).

An array plus a map of positions Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • The invariant, and the ticket that fills its own hole
  • A dry run
  • The code in five languages
  • Uniform draws, tested twice
  • Five bugs reproduced, each shrunk to a few calls
  • Repeated tickets: a set of positions per number
  • Variants
  • Exercise: the raffle box
    • A raffle box with insert, remove and random draws in O(1)
  • A practice set with answers
  • Check yourself
  • Interview questions
    • Why can't a hash set on its own return a uniformly random element in O(1)?
    • A C++ raffle draws a position with rand() % n. On a platform where RAND_MAX is 32,767, a statistics check with n = 20,000 tickets shows that some tickets win twice as often as others. Explain exactly which ones and why, and fix the draw.
    • Tickets can now repeat: a family may hold three copies of number 7, and every copy must be as likely to win as any other. Change insert, remove and getRandom, keep each O(1) on average, and walk through removing a copy that is the last element of the array.
    • A lucky-draw app picks a winning seat from 0 to n − 1, where n can be 10⁹, but k seats, up to 10⁵ of them, belong to staff and may never win. Each pick must take O(1) after setup, memory must be O(k), and every other seat must be equally likely. Design it.
  • Key takeaways

1 diagram · 6 runnable examples (C++, Go, Java, JavaScript, Python, raffle_session.py, session.py) · 1 exercise with sample tests · 3 interview answers · about 25 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.