</> MAANG.io
coding interview Β· 101

Foundations

Master coding interviews with comprehensive coverage of data structures, algorithms, and problem-solving techniques. Progress from fundamentals to advanced topics with expertly curated content.

0/255 solved 0% complete

Data Structure Design

What is this?

Here someone hands you a wish list of operations β€” "add a number, remove a number, and give me a random one, all instantly" β€” and your job is to build a container that grants every wish at full speed. It is like designing a backpack with the perfect set of pockets so that whatever you reach for comes out immediately. The catch: one kind of pocket can't do it all, so you stitch two simple structures together and let each cover the other's weak spot.

flowchart TD A["List the operations and their target speeds"] --> B["Pick the structure that makes each op cheap"] B --> C["No single one fits, so pair two structures"] C --> D["Keep them in sync via one invariant"]

πŸ’‘ Fun fact: The LRU cache built in this chapter β€” a hash map glued to a doubly linked list β€” is exactly how real systems decide what to keep in fast memory. It powers browser caches, CPU caches, and databases like Redis, all to hit that magic O(1) lookup and eviction.

πŸ”“ The 5 problems in this chapter are free. Sign in with Google or Microsoft to start solving.


Core idea: You're handed an API with required complexities and must build a structure that honors all of them at once. Since operations pull in different directions, the winning move is almost always to combine two simple structures so each does what it's best at β€” turning every operation into O(1) or O(log n).


The method

  1. List the operations and their target complexities. That's the spec.
  2. For each op, name the structure that makes it cheap (lookup β†’ hash map; random index β†’ array; ordering/recency β†’ linked list/heap; windowed/versioned β†’ ring buffer / history + bisect).
  3. If no single structure covers all ops, pair two and keep them in sync.
  4. State the invariant β€” what's always true between calls (e.g. "the map and the list reference the same nodes"). Bugs live where the invariant slips.

The five problems

Data structure design branching to its five problems: Circular Queue (ring buffer), Insert/Delete/GetRandom O(1) (array + index map), Hit Counter (fixed-bucket ring), LRU Cache (map + doubly linked list), Snapshot Array (per-cell history + bisect)

Problem Structures combined Key op made cheap
Design Circular Queue array + head + count, % k O(1) bounded FIFO
Insert Delete GetRandom O(1) dynamic array + value→index map O(1) remove via swap-to-end
Design Hit Counter 300 fixed buckets keyed by t % 300 O(1) windowed count, bounded memory
LRU Cache hash map + doubly linked list O(1) get/put + recency eviction
Snapshot Array per-index (snap_id, val) log + binary search versioned read ∝ writes, not snapsΓ—length

The progression builds the core trick: Circular Queue introduces the ring; RandomizedSet the array+map pairing; LRU the canonical map+DLL combo; Hit Counter and Snapshot Array show the same "don't store more than you need" instinct for time windows and versions.


The recurring trade

No single structure is great at everything. A hash map finds fast but has no order; an array indexes fast but deletes slowly; a linked list reorders fast but can't random-access. Pair them β€” map+DLL, array+map β€” and each covers the other's weakness. That pairing is data-structure design.


πŸ““ Draw it yourself

  1. LRU map+DLL. Draw the hash map pointing into a doubly linked list; promote on get, evict the tail on overflow.
  2. Swap-to-end. For RandomizedSet, delete a middle element by swapping with the last and popping β€” update the moved element's index.

Snap photos and embed them with the /host-diagrams skill.


Key takeaways

  • Start from the API + complexities; pick structures that make each op cheap.
  • Combine two structures when one can't do it all (map+DLL, array+map) β€” the defining pattern.
  • Bound your memory: ring buffers for windows (Hit Counter), change-logs for versions (Snapshot Array).
  • State and preserve the invariant linking the two structures β€” that's where bugs hide.
  • Why interviewers love it: it's how real caches, queues, and indexes are actually built.

Order: Design Circular Queue β†’ Insert Delete GetRandom O(1) β†’ Design Hit Counter β†’ LRU Cache β†’ Snapshot Array.

Insert Delete GetRandom O(1)

Core idea: Keep the elements in a plain array so you can grab a random index in O(1), and keep a hash map value β†’ index so you can find any element in O(1) β€” the only tricky part, deleting from the middle of an array without leaving a hole, is solved by swapping the victim with the last element and popping the tail.

This problem is a small gem because it forces you to admit that no single built-in data structure does everything you need. A set gives O(1) membership but can't hand you a uniformly random element in O(1). A list gives O(1) random indexing but O(n) removal. The interview is really asking: can you combine two structures so each covers the other's weakness? Let's build it up from the clumsy single-structure attempts to the clean two-structure answer.


Problem, rephrased

Imagine you're running a live raffle drum at a fair.

  • A new ticket can be dropped in the drum with insert(ticket). If that exact ticket is already inside, nothing changes and you report "already in" β€” no duplicate tickets allowed.
  • A ticket can be pulled out by name with remove(ticket) (someone left early and wants their ticket back). If it isn't in the drum, you report "not found."
  • At any moment the announcer spins the drum and get_random() must return a ticket, with every ticket currently inside equally likely to come out.

The drum is constantly changing β€” tickets going in and out all evening β€” and every one of these three actions must feel instant (average O(1)), no matter how full the drum gets.

Operation Input Returns Behavior
insert(x) a value True if newly added, False if already present no duplicates
remove(x) a value True if removed, False if absent must not break O(1) random access
get_random() β€” a value currently stored uniformly random over the current set

The get_random() row is what rules out the obvious answers, and the remove(x) row is where all the cleverness lives.


Sign in to continue reading

The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.

Sign in to read the full lesson

Sign in to MAANG.io

Use your Google or Microsoft account β€” no password to remember.

Continue with Google Continue with Microsoft

Please accept the terms above to continue.