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

Frequency stack and a constant-time counter

Design a maximum frequency stack and a counter whose every call is O(1): an original DJ-booth problem, the scan that fails at 10⁵ calls, and what to notice.

  • Advanced
  • 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: 9 of 35 minutes free
Free: The problem: a DJ's request box. With a pass: the other 10 sections and 3 interview answers, about 26 minutes.

What you will learn

  • Implement a stack that pops the most frequent value in O(1), newest first on ties, with a stack per count level
  • Prove the level design correct with an invariant, including the step after a level runs empty
  • Compare the level stacks with a heap of (count, arrival) entries, with measured comparisons
  • Build counters whose increment, decrement, maximum and minimum are all O(1), from linked count buckets
  • Find tie-breaking and bookkeeping bugs with random tests against a scan

Before you start

What you’ll learn · Before you start

What you will learn

  • Implement a stack that pops the most frequent value in O(1), newest first on ties, with a stack per count level
  • Prove the level design correct with an invariant, including the step after a level runs empty
  • Compare the level stacks with a heap of (count, arrival) entries, with measured comparisons
  • Build counters whose increment, decrement, maximum and minimum are all O(1), from linked count buckets
  • Find tie-breaking and bookkeeping bugs with random tests against a scan

Before you start

On this page

A maximum frequency stack is a stack whose pop() removes the value that occurs most often on it, and among values that occur equally often, the one pushed most recently. A constant-time counter is its sibling: it counts keys up and down and can name a key with the most or the fewest counts, each call in O(1). Both appear wherever a system must keep serving “the most requested” while requests keep arriving: song requests, trending tags, busy pages. Counting on every call is correct and quadratic overall. The rest of this opening sets out an original request-box problem, counts what its brute force costs, and names the observation behind both O(1) designs.

The problem: a DJ’s request box

The problem

At a wedding, guests drop request slips for songs into a box. Write a class RequestBox() with two methods. request(song) adds a slip for song. play() picks the song with the most slips in the box, takes out one of its slips, the newest, and returns the song; when several songs tie for the most slips, it picks the one whose newest slip arrived last. Limits: up to 2 × 10⁵ calls, song names of up to 30 characters, and play() is only called when the box holds a slip.

The box is a frequency stack with songs as its values. Six requests, for waltz, salsa, waltz, salsa, tango and waltz, followed by six plays, give waltz, salsa, waltz, tango, salsa and waltz. The second play settles a tie: waltz and salsa both have two slips left, and salsa’s second slip is newer. In a second test, requests for jive and polka and one play give polka.

Six request slips in arrival order, each marked as its song’s first, second or third slip, and the order play() takes them in.Six slips, oldest firstplay() six times:waltz, salsa, waltz, tango, salsa, waltzwaltz1st slipsalsa1st slipwaltz2nd slipsalsa2nd sliptango1st slipwaltz3rd slipmost slips first;on a tie, the newest slip

The example: six slips, and the order in which play() takes them out

Text description of the diagram

The box holds six slips, oldest first, in two rows of three. The first row: waltz, its 1st slip; salsa, its 1st slip; waltz, its 2nd slip. The second row: salsa, its 2nd slip; tango, its 1st slip; waltz, its 3rd slip.

An arrow labelled "most slips first; on a tie, the newest slip" leads to the order in which six calls of play() empty the box: waltz, salsa, waltz, tango, salsa, waltz. waltz goes first with three slips. Then waltz and salsa have two each, and salsa's second slip is newer, so salsa goes. Then waltz has two and the others one, so waltz goes again. Then all three songs have one slip, and tango's is the newest, so tango goes, followed by salsa and finally waltz.

The brute force: count every slip on every play

Keep the slips in a list. Each play() counts the slips of every song and remembers each song’s newest slip, then takes out the winner’s newest slip. A request costs O(1), but a play reads every slip still in the box. The slowest test makes n requests and then plays them all:

The scan, with every slip each play reads counted Python · brute/scan_box.py
"""The first design that comes to mind: keep every slip in a list, and on each play() count the slips of every song
to find the winner. The answers are right; this program counts the slips each play() reads on the slowest test.
"""


class ScanBox:
    def __init__(self):
        self.slips = []  # songs in the order their slips arrived
        self.reads = 0

    def request(self, song):
        self.slips.append(song)

    def play(self):
        count, newest = {}, {}
        for i, song in enumerate(self.slips):
            self.reads += 1
            count[song] = count.get(song, 0) + 1
            newest[song] = i
        best = max(count, key=lambda s: (count[s], newest[s]))  # most slips, then the newest slip
        del self.slips[newest[best]]  # take that song's newest slip out of the box
        return best


box = ScanBox()
for song in ["waltz", "salsa", "waltz", "salsa", "tango", "waltz"]:
    box.request(song)
print("example:", [box.play() for _ in range(6)])

# The slowest test: n requests spread over 100 songs, then n plays, each of which reads every slip left.
for n in (500, 1_000, 2_000):
    box = ScanBox()
    for i in range(n):
        box.request(f"song-{i % 100}")
    for _ in range(n):
        box.play()
    print(f"n = {n:,}: {n:,} plays read {box.reads:,} slips")

n = 100_000
print(f"n = {n:,}: {n:,} plays would read {n * (n + 1) // 2:,} slips")

Output

example: ['waltz', 'salsa', 'waltz', 'tango', 'salsa', 'waltz']
n = 500: 500 plays read 125,250 slips
n = 1,000: 1,000 plays read 500,500 slips
n = 2,000: 2,000 plays read 2,001,000 slips
n = 100,000: 100,000 plays would read 5,000,050,000 slips

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

n plays read about n²/2 slips: 5 × 10⁹ at n = 10⁵, some eight minutes of CPython.

What to notice

Number each song’s slips: its first slip, its second, and so on. A song with c slips holds exactly one slip of each number from 1 to c. The song to play always has the highest number in use, and among the songs that have it, the newest slip with that number wins. So group the slips by number, keep each group in arrival order, and the answer is the newest slip of the highest group. Three designs build on this:

  • A stack per count level keeps each group as a stack and remembers the highest level in use.
  • A heap of (count, arrival) entries pushes one entry per slip and pops the largest.
  • A constant-time counter links its count buckets in a list, so increments, decrements and both extremes are O(1).

Stacks per count level, step by step Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Why the top drops by exactly one: the invariant and a dry run
    • Random tests against the scan
  • A heap version, and what it pays
  • The code in five languages
  • A constant-time counter: up, down, the most and the fewest
  • Edge cases and common bugs
  • Exercise with sample tests
    • A stack that pops the most frequent value first
  • A practice set with sample answers
  • Check yourself
  • Interview questions
    • Why does a maximum frequency stack keep one stack per count level instead of one stack per value?
    • In a frequency stack with one stack per count level, a pop sometimes empties the highest level. How does the design find the new highest level, why is that always right, and what breaks if pops may remove any value?
    • Your team's frequency stack pushes (count, arrival time, value) into a heap and pops the largest. Compare it with stacks per count level on cost, memory and the bugs each invites, and say when you would keep the heap.
    • Design live page-view counters with inc(page), dec(page), max_page() and min_page(), each in O(1), for a dashboard of a million pages. Why is a hash map with a heap, or a single max pointer, not enough?
  • Key takeaways

7 runnable examples (C++, Go, Java, JavaScript, Python, check.py, request_box.py) · 1 exercise with sample tests · 3 interview answers · about 24 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.