InterviewLegend

Fundamentals and Low-Level Design

Alex Chen35 min readUpdated 68 real interview reports
In this lesson
  • Run the OOD round in order: requirements, classes, method contracts, then how the design extends
  • Build an LRU cache live, then work two problems where the right structure is the solution
  • Answer the oral follow-ups on OOP, memory and ownership, and complexity

This is the round the India loop is decided on, and the one the C++ track spends the most time in. After the coding screen, BlackRock turns to two things the screen barely touches. Can you design a small system as clean classes? Do you understand the tools underneath the code you write? The set piece is a machine-coding exercise, usually build a cache and make it run. Around it sit medium data-structure problems where the trick is choosing the right structure. A steady drip of oral follow-ups covers object orientation, memory, and complexity. This lesson builds the cache, works two structure-choice problems in full, and then drills the fundamentals questions you will have to answer out loud.

The BlackRock angle
The interviewer is grading judgment more than trivia recall. A machine-coding prompt at BlackRock is deliberately not algorithmically hard. An LRU cache is a hashmap and a linked list. It's asked because doing it well forces every skill the loop cares about into one exercise. You pick the structures, draw sane class boundaries, reason about the map's cost, and handle concurrency if pressed. So slow down where a screen would speed up. State your assumptions before you type, name the classes out loud, keep the eviction policy behind a seam you could swap, and narrate the O(1) claim as you make it. The finished code matters less than the sequence of decisions the interviewer watched you make.

How BlackRock runs the OOD round

The low-level-design round has a shape, and following it is half of a strong performance. You're handed a small, concrete thing to build: a cache, a rate limiter, a producer-consumer buffer, an in-memory store. You get roughly forty minutes to design and implement it while you talk. The failure mode is jumping straight to a single fat class and coding. The interviewer wants to see you move through four moves in order, and each move is a place to earn signal.

1

Clarify the requirements

Pin down the operations and their contracts before any code. What methods exist, and what does each return on a miss? Is there a fixed capacity, and what's the eviction policy? Does it need to be thread-safe, and are keys and values generic? Two minutes of questions here stops you from designing the wrong thing.

2

Name the classes and interfaces

Sketch the types out loud. A cache is an interface with get and put; one implementation carries one eviction policy. A linked-list node is its own small class. Putting the policy behind an interface later lets you swap LRU for LFU without touching the callers.

3

Define the method contracts, then fill them

Write the signatures first, agree on what each does at the boundary, then implement. Get returns the value or a miss sentinel and counts as a use; put inserts or updates and evicts when full. Coding against agreed contracts keeps the implementation honest.

4

Defend the extension

The interviewer will move the constraint: now make it thread-safe, now switch to LFU, now add a time-to-live. A design with clean seams answers each in a sentence about which class changes. A tangled one has to be rewritten. This is where the round is won.

Written as a skeleton before a single method body exists, a strong cache design reads like this. One interface, one implementation per policy, a small node type, and the recency structure declared up front:

java
interface Cache<K, V> {
V get(K key); // the value, or a miss sentinel
void put(K key, V value); // insert or update, evicting when full
}
// One implementation carries one eviction policy, hidden behind the interface.
final class LruCache<K, V> implements Cache<K, V> {
private final int capacity;
private final Map<K, Node<K, V>> index; // O(1) lookup by key
private final Node<K, V> head, tail; // sentinels: most- and least-recent ends
LruCache(int capacity) { /* wire head <-> tail, size the index */ }
public V get(K key) { /* touch the node, move to front, return */ }
public void put(K key, V val) { /* upsert, move to front, evict if over capacity */ }
private void evict() { /* unlink the node before tail, drop it from index */ }
}
// A tiny class of its own, not an anonymous tuple.
class Node<K, V> { K key; V value; Node<K, V> prev, next; }
Say the assumptions before you write the class. The single cheapest way to look senior in this round is to open with the clarifying questions: fixed capacity, miss returns a sentinel, single-threaded unless told otherwise, keys hashable. It costs two minutes and it frames every later decision. Candidates who skip it design a data structure that solves a problem the interviewer did not ask for, then lose time reworking it when the real constraint surfaces.

Build it: an LRU cache in O(1)

BlackRock caches reference data, security-master rows and latest prices, so a hot lookup never pays for a database round trip. The cache holds a fixed number of entries; when it fills, the least-recently-used entry is dropped to make room. Both operations have to run in constant time, which is the constraint that rules out anything linear and forces the two-structure design. Build it as clean classes, and be ready for the interviewer to ask for the eviction policy to change.

1

Reference-Data Cache: LRU Cache

MediumCommonly asked
Problem
Design a fixed-capacity cache with least-recently-used eviction. Implement a class LRUCache:

- LRUCache(capacity) creates a cache that holds at most capacity entries.
- get(key) returns the value stored for key, or -1 if the key is not present. A successful get counts as a use, making that entry the most recently used.
- put(key, value) inserts the entry, or updates it if the key already exists, and marks it most recently used. If inserting pushes the size above capacity, evict the least-recently-used entry first.

Both get and put must run in O(1) average time.

Input: LRUCache(2); put(1,1); put(2,2); get(1); put(3,3); get(2); put(4,4); get(1); get(3); get(4)

Output: [null,null,null,1,null,-1,null,-1,3,4]

put(3,3) evicts key 2 (least recent), so get(2) is -1. put(4,4) evicts key 1, so get(1) is -1. Keys 3 and 4 remain.

How this uses the pattern

Two structures cooperate. A hashmap keyed by key gives O(1) lookup to a node. A doubly linked list holds the nodes in most-to-least recently used order. Touching a node means unlinking it and re-inserting it at the front, and eviction means dropping the node at the back.

Reference-Data Cache: LRU Cache

Loading...
Click "Run" to execute your code

Test Cases

Input: [["LRUCache",[2]],["put",[1,1]],["put",[2,2]],["get",[1]],["put",[3,3]],["get",[2]],["put",[4,4]],["get",[1]],["get",[3]],["get",[4]]]Expected: [null,null,null,1,null,-1,null,-1,3,4]
Input: [["LRUCache",[1]],["put",[1,10]],["get",[1]],["put",[2,20]],["get",[1]],["get",[2]]]Expected: [null,null,10,null,-1,20]
Input: [["LRUCache",[2]],["put",[1,1]],["put",[1,5]],["get",[1]],["put",[2,2]],["put",[3,3]],["get",[1]]]Expected: [null,null,null,5,null,null,-1]
Input: [["LRUCache",[2]],["get",[9]],["put",[1,1]],["get",[1]],["get",[2]]]Expected: [null,-1,null,1,-1]
Input: [["LRUCache",[3]],["put",[1,1]],["put",[2,2]],["put",[3,3]],["get",[1]],["put",[4,4]],["get",[2]],["get",[3]],["get",[4]],["get",[1]]]Expected: [null,null,null,null,1,null,-1,3,4,1]
The BlackRock angle
An LRU cache is the whole BlackRock bar folded into one exercise. It is easy algorithmically and hard to do cleanly, which is exactly the split the loop probes. The crux is the two-structure insight: a hashmap for lookup and a doubly linked list for order. Reach for it and the O(1) requirement is met. Reach for a single array and every eviction turns linear. Keep the node type as its own class and the list operations as small private methods. The follow-up that changes the policy to LFU rewards a design with clean seams and punishes one big method.
Take it further
Switch the eviction policy to LFU. Least-frequently-used keeps a use-count per entry and evicts the smallest count, breaking ties by recency. The clean way is a map from frequency to a list of entries at that frequency, plus a running minimum frequency, so eviction stays O(1). The ask really tests your interface. If get and put call a small policy object rather than inlining LRU logic, you swap the policy and leave the cache class untouched. If you inlined it, you rewrite.

Structure choice: days until a higher price

The second and third problems here are not about a class. They are about the fundamentals question underneath every medium prompt: which data structure turns a quadratic brute force into a linear pass. This one looks like a simple scan of a price series, and the naive answer, for each day look forward until a higher price appears, is O(n squared). A monotonic stack does it in one pass. Recognizing that the answer is a stack, and which order to keep it in, is the entire test.

2

Price Series: Days Until a Higher Price

MediumCommonly asked
Problem
You are given prices, a list where prices[i] is a security's closing price on day i. For each day, find how many days you would have to wait for a strictly higher closing price.

Return a list answer where answer[i] is that number of days, or 0 if no later day has a strictly higher price.

Input: prices = [73,74,75,71,69,72,76,73]

Output: [1,1,4,2,1,1,0,0]

Day 0 (73) waits 1 day for 74. Day 2 (75) waits until day 6 (76), 4 days out. Days 6 and 7 never see a higher price, so 0.

Input: prices = [60,50,40,30]

Output: [0,0,0,0]

Prices only fall, so no day ever has a higher price ahead of it.

How this uses the pattern

Keep a stack of day indices whose higher price has not been found yet, with prices decreasing from bottom to top. When today beats the price at the top of the stack, today is that day's answer, so pop it and record the day gap. Then push today.

Price Series: Days Until a Higher Price

Loading...
Click "Run" to execute your code

Test Cases

Input: [[73,74,75,71,69,72,76,73]]Expected: [1,1,4,2,1,1,0,0]
Input: [[30,40,50,60]]Expected: [1,1,1,0]
Input: [[60,50,40,30]]Expected: [0,0,0,0]
Input: [[89,62,70,58,47,47,46,76,100,70]]Expected: [8,1,5,4,3,2,1,1,0,0]
Input: [[5,5,5,5]]Expected: [0,0,0,0]
Input: [[100]]Expected: [0]
Input: [[]]Expected: []
The BlackRock angle
This is the C++ track's monotonic-stack question, dressed as a price series instead of temperatures. The single decision that matters is the stack's invariant. It holds the indices of days still waiting, kept in decreasing price order, so a new higher price resolves a run of them at once. Candidates who miss the stack write the O(n squared) double loop, and it works on the sample. That's the trap. The interviewer wants the linear pass and the reasoning that each index is pushed and popped exactly once.
Take it further
Report the span behind each day instead of the wait ahead. The stock-span variant asks, for each day, how many consecutive prior days had a price at or below today's. Same tool, mirrored: keep a stack of prior indices with strictly higher prices, pop while the top is at or below today, and the span is the gap to whatever remains. One monotonic stack answers both the forward and backward versions.

Complexity trade-off: the longest consecutive run

The last problem is where BlackRock most directly probes complexity reasoning. It has an obvious answer and a better one, and the interviewer wants to hear you compare them. Given a bag of integer ids, find the longest run of consecutive values. Sorting gets you there in O(n log n) and is a perfectly reasonable first offer. A hash set gets you O(n), and the leap is seeing how to avoid re-counting the same run. Say the sort solution, state its cost, then improve it out loud.

3

Trade IDs: Longest Consecutive Run

MediumSometimes asked
Problem
You are given ids, an unsorted list of integer identifiers that may contain duplicates. A consecutive run is a set of ids that form an unbroken sequence of integers, such as 4, 5, 6, 7.

Return the length of the longest consecutive run present in ids. Aim for O(n) time, faster than sorting.

Input: ids = [100,4,200,1,3,2]

Output: 4

The run 1, 2, 3, 4 has length 4. The values 100 and 200 are isolated.

Input: ids = [0,3,7,2,5,8,4,6,0,1]

Output: 9

The values 0 through 8 form one unbroken run of length 9; the duplicate 0 does not extend it.

How this uses the pattern

Put every id in a hash set for O(1) membership. A value starts a run only if the value one below it is absent from the set. From each such run-head, walk upward while the next value is present, counting the length. Each value is visited at most twice overall.

Trade IDs: Longest Consecutive Run

Loading...
Click "Run" to execute your code

Test Cases

Input: [[100,4,200,1,3,2]]Expected: 4
Input: [[0,3,7,2,5,8,4,6,0,1]]Expected: 9
Input: [[]]Expected: 0
Input: [[9]]Expected: 1
Input: [[5,5,5]]Expected: 1
Input: [[10,20,30]]Expected: 1
Input: [[1,2,0,1]]Expected: 3
The BlackRock angle
This problem is a complexity conversation in disguise, and BlackRock wants both halves of it. Offer the sort first, O(n log n), then improve to the hash-set O(n) and explain why it is still linear despite the inner while loop. The trick is to start a count only at a run-head, a value whose predecessor is missing. The interior of any run is never a starting point. Skip that guard and you re-walk each run from every one of its members and quietly slide back to quadratic.
Take it further
Return the run itself, not just its length. Track the best length alongside the value that started the best run, then rebuild the sequence from that start once the scan finishes. It stays O(n) because you still visit each value a constant number of times and reconstruct only the single winning run at the end.

The fundamentals oral: OOP, memory, complexity

Every problem above trails a set of spoken follow-ups, and the interviewer uses them to tell an engineer who understands their tools from one who memorized a pattern. They are the questions a colleague would ask while reading your code, not gotchas. Have crisp, spoken answers ready across three areas, tied to the code you just wrote rather than recited from a glossary.

Object orientation. Expect why you drew the class boundaries you did. Why is the eviction policy behind an interface? So a new policy is a new class, not an edit to the cache. Why is the linked-list node its own type rather than a tuple? So it can carry its key and its neighbors and stay readable. Be ready for compile-time versus runtime polymorphism: overloading resolved by the compiler against static types, overriding dispatched at runtime against the real object. Encapsulation comes up too, as the reason the cache exposes get and put but hides the list surgery. The honest answer to why bother is that the follow-up will change the requirements, and clean boundaries are what let you answer in a sentence.

Memory and ownership. This is where Java and C++ diverge, and BlackRock probes the language you claimed. On the Java side, know what lives on the stack versus the heap. A local reference sits on the stack while the object it points to lives on the heap. Know how garbage collection decides an object is unreachable and reclaimable. On the C++ side of the same cache, expect who owns the nodes and why a smart pointer expresses that ownership. Then the difference between a unique owner and a shared, reference-counted one, and how RAII ties a node's lifetime to a scope. If you mention move semantics, be able to say what moving a node does that copying it would not, and why that matters for a container that reshuffles pointers constantly.

Complexity. Do not just state a Big-O; justify it against the structure you chose. Why is the cache O(1) per operation when it clearly does several steps? Because each step is a constant number of map operations and pointer writes, none of which scale with the number of entries. Why is the price sweep O(n) when it has a while loop inside a for loop? Because each index enters and leaves the stack once, so the inner work is amortized constant. Why does the consecutive-run guard preserve O(n)? Because it caps each value at two visits. The pattern the interviewer rewards is naming the structure, then deriving the bound from how the structure behaves, rather than pattern-matching a number to a shape.

Tie every fundamentals answer back to the code on the screen. Asked about the heap, point at the nodes your cache allocates. Asked about polymorphism, point at the policy interface you put the eviction behind. Asked about O(1), walk the two pointer writes that make a get constant. An answer anchored to the artifact in front of you reads as understanding; the same fact recited in the abstract reads as revision. That anchoring is the difference this round is built to detect.

Common questions

What is the BlackRock machine-coding round?

It is a low-level-design exercise where you design and implement a small system live, most often a cache, in about forty minutes while narrating your choices. The canonical prompt is an LRU cache: a hashmap for O(1) lookup married to a doubly linked list for recency, written as clean classes. The grading is object-oriented judgment, sensible interfaces, the right structures, and a design that survives the follow-up asking you to change the eviction policy or add thread-safety.

How do I design an LRU cache in an interview?

Move in order. Clarify the operations and their contracts, then name the types: a cache interface with get and put, one implementation, and a small node class. Use a hashmap from key to node for constant-time lookup and a doubly linked list for recency, with head and tail sentinels so the pointer surgery has no edge cases. Touch a node on every access to keep it fresh, and evict from the tail when you overflow. Store the key on each node so eviction can also clean the map.

What fundamentals does BlackRock ask about?

The oral follow-ups cluster in three areas. Object orientation covers why your class boundaries, compile-time versus runtime polymorphism, and encapsulation. Memory covers stack versus heap and garbage collection in Java, plus smart pointers, ownership, RAII and move semantics in C++. Complexity means justifying a Big-O from how the chosen structure behaves instead of reciting it. The strongest answers anchor to the code you just wrote rather than to definitions.

Should I use Java or C++ for the design round?

Use the language you can defend under questioning, because the round drills whichever you pick. The India loop leans Java, so expect JVM memory and collection internals; the C++ track mirrors the same exercises and probes ownership, smart pointers, and move semantics instead. Python is accepted as a working language. The structures and the design are identical across all three; only the memory-model follow-ups change.

How is this round different from the coding screen?

The screen tests whether you can solve an easy-to-medium problem quickly. This round tests whether you can design cleanly and explain deeply. The problems here are deliberately not hard, an LRU cache, a monotonic stack, a hash-set scan, but each carries a design decision and a chain of oral follow-ups. Prepare by building the cache end to end and by rehearsing the spoken justification for every structure you reach for.

Key Takeaways

  • ✓The BlackRock low-level-design round is a forty-minute machine-coding exercise, usually a cache. Move through it in order: clarify the requirements, name the classes and interfaces, define the method contracts, then defend how the design extends when the policy or the concurrency changes.
  • ✓Build an LRU cache from a hashmap for O(1) lookup and a doubly linked list for recency, with head and tail sentinels to erase edge cases. Store the key on each node so eviction cleans the map, touch a node on every access, and evict from the tail. Putting the eviction policy behind an interface is what makes the LFU follow-up a one-line answer.
  • ✓The medium problems are structure-choice questions in disguise. Days-until-a-higher-price is a monotonic stack of waiting-day indices, O(n) because each index is pushed and popped once. Longest-consecutive-run is a hash set with a run-head guard, O(n) instead of the sort's O(n log n) because each value is visited at most twice.
  • ✓The fundamentals oral decides this round. Have crisp answers on OOP, memory and ownership across Java and C++, and complexity. Anchor every one to the code on the screen rather than reciting a definition.
Had your BlackRock interview?Tell us what they asked. Reviewed before it goes in, never with your name.
BlackRock interview guide

Know what BlackRock asks before you walk in.

13 coding · 3 system design · 15 behavioral questions

Get Premium$49 a year · Cancel anytime
Or start freeNo card.

Get their questions first.

InterviewLegend is independent and not affiliated with or endorsed by any company it covers.