One candidate's Glassdoor writeup of Bloomberg's entry-level software engineer loop reads almost like a checklist: a phone screen on pointer basics and a browser-history LRU variant, two video rounds on run-length encoding and BST validation, an onsite in London with a modified bracket-matching problem, and a manager round built entirely around a stock-trading design question, capped off by an HR call about salary and why Bloomberg specifically. Candidates report the loop runs five to six stages, and it took this person about three weeks start to finish. That length and that mix, DSA rounds stacked next to a manager round that's really a behavioral interview in disguise, shows up again and again across reports.
What makes Bloomberg's process distinct isn't exotic technical content. Reported coding questions lean on the same array, string, and tree fundamentals you'd see anywhere: longest substring without repeating characters, merge overlapping intervals, validate a binary search tree. The difference is context. A stock-price lookup structure or a scrabble-scoring function shows up more than a generic "reverse a linked list," and system design questions get framed around a trading desk or a market-data feed instead of a generic web app. Bloomberg also runs its own team-matching process after the loop closes, so who you talk to in round three isn't necessarily who you'd report to (Glassdoor, Bloomberg Software Engineer interview reviews).
This page groups reported questions into seven rounds: the recruiter screen and online assessment, the coding phone screen, onsite data structure rounds, system design for low-latency financial systems, code review and engineering-practice questions, behavioral and culture-fit questions, and the manager/HR final round. Where reports thin out on specifics, we say so instead of inventing detail.
Recruiter screen and online assessment
The first real filter for most candidates is a HackerRank or CoderPad-style OA with two coding problems and a short design question, timed around 60-90 minutes. The recruiter call itself is mostly background and logistics, but it's also where "why Bloomberg" starts getting asked, and reviewers note it keeps getting asked at every stage after this one too.
Easy questions
15This isn't really a test of your resume, the recruiter has already read it. It's a test of whether your story is coherent and whether you've done five minutes of homework on what Bloomberg actually builds. A weak answer talks about "a great company with a strong reputation." A strong answer names something concrete, the terminal, the fixed-income data business, the fact that engineering here sits inside a company whose product is information delivered under a latency budget, and connects that to something specific in your own background.
I'd structure it as three sentences: what you've built, what kind of problems you gravitate toward, and why those problems show up at Bloomberg more than at a typical product company. Keep it under two minutes. Recruiters are screening for a red flag, not scoring an essay.
Reports converge on two coding problems plus a lighter design or logic question, done through HackerRank, usually under a 60 to 90 minute window depending on level. The coding problems are typically medium-difficulty array, string, or tree problems, not the kind that require an unusual algorithm you'd only see in a competitive programming archive.
The design piece is often smaller than "system design" implies at the onsite stage, think a data structure that supports a couple of operations efficiently rather than a distributed architecture. Treat the OA like a timed exam: read both problems first, do the one you're more confident about first, and leave yourself ten minutes to review edge cases on both before submitting.
Less than candidates assume, but it's not zero. The recruiter call is a gate, not a scoring round, its job is to catch mismatches on comp expectations, visa status, or location before anyone spends an hour of engineer time on you. Fail the technical rounds and a great recruiter call won't save you. But a genuinely bad recruiter call, evasive answers, no clear reason for wanting the role, can end things before you reach a single line of code.
Sliding window is the right tool here. Keep two pointers marking the current window and a hash set (or a hash map of character to last-seen index) tracking what's inside it. Expand the right pointer one character at a time; if the character is already in the window, move the left pointer forward past its previous occurrence instead of resetting to zero, which is the mistake that turns an O(n) solution into O(n²).
def length_of_longest_substring(s: str) -> int:
last_seen = {}
left = 0
best = 0
for right, ch in enumerate(s):
if ch in last_seen and last_seen[ch] >= left:
left = last_seen[ch] + 1
last_seen[ch] = right
best = max(best, right - left + 1)
return bestSay the complexity out loud without being asked: O(n) time, O(min(n, alphabet size)) space. That's usually the follow-up question anyway.
Sort the intervals by start time first, this single step is what makes the rest of the problem linear instead of quadratic. Then walk through once, keeping a running merged interval: if the next interval's start is less than or equal to the current merged interval's end, extend the end; otherwise close out the current merged interval and start a new one.
def merge(intervals):
intervals.sort(key=lambda x: x[0])
merged = [intervals[0]]
for start, end in intervals[1:]:
if start <= merged[-1][1]:
merged[-1][1] = max(merged[-1][1], end)
else:
merged.append([start, end])
return mergedStraightforward single pass: track the current character and a running count, and every time the character changes, append the previous character and its count to the result before resetting the counter.
def run_length_encode(s: str) -> str:
if not s:
return ""
result = []
count = 1
for i in range(1, len(s) + 1):
if i < len(s) and s[i] == s[i - 1]:
count += 1
else:
result.append(s[i - 1] + str(count))
count = 1
return "".join(result)The follow-up worth anticipating: what if the encoded string is longer than the original for short runs? A production version usually has a fallback that returns the original string when encoding doesn't shrink it.
A stack solves this directly: push every opening bracket, and on a closing bracket, check the top of the stack matches its corresponding opener. If it doesn't match, or the stack is empty when you hit a closer, the string is unbalanced. At the end, the stack must be empty too, otherwise there are unclosed openers.
def is_balanced(s: str) -> bool:
pairs = {")": "(", "]": "[", "}": "{"}
stack = []
for ch in s:
if ch in "([{":
stack.append(ch)
elif ch in pairs:
if not stack or stack.pop() != pairs[ch]:
return False
return not stackReports describe an onsite variant that's modified, extra bracket types, or brackets embedded in a larger string with other characters to skip. Ask clarifying questions about what characters can appear before assuming it's the textbook version.
Dereferencing a null pointer is undefined behavior in C++, in practice it usually segfaults immediately because address zero is deliberately left unmapped, but the standard doesn't guarantee that, it could just as easily corrupt memory silently on some platforms. That's why production code checks for null before dereferencing rather than relying on the crash to surface the bug loudly.
A reference must be initialized when declared and can never be reseated to refer to something else afterward. A pointer can be null, can be reassigned to point elsewhere, and needs explicit dereferencing with * to access the underlying value. The practical rule most C++ teams follow: use a reference when a value must exist and won't change what it refers to, use a pointer when the value might not exist or ownership needs to be reassignable.
Trial division works but is slow for a repeated range query. The Sieve of Eratosthenes is the expected answer: mark a boolean array as prime by default, then for every number starting at 2, if it's still marked prime, mark all of its multiples as not prime. Anything left marked true at the end is prime.
def sieve(n):
is_prime = [True] * (n + 1)
is_prime[0] = is_prime[1] = False
for i in range(2, int(n ** 0.5) + 1):
if is_prime[i]:
for multiple in range(i * i, n + 1, i):
is_prime[multiple] = False
return [i for i in range(n + 1) if is_prime[i]]Time complexity is O(n log log n), and the follow-up worth pre-empting is why you start marking multiples at i squared rather than 2i, everything below i squared has already been marked by a smaller prime factor.
Clarify the actual requirements before drawing a single box, specifically the read/write ratio, the latency budget, and whether strict consistency or eventual consistency is acceptable for this particular use case. Those three answers change the entire shape of the design, and guessing at them is the single most common way candidates waste fifteen minutes designing the wrong thing well.
Once you have those, sketch the obvious, simple version first, even if you already know it won't scale, then explicitly walk through where it breaks and why, and only then introduce the more complex component that fixes that specific bottleneck. Interviewers reportedly want to see that reasoning chain, not a finished architecture diagram you memorized from a blog post.
An array-backed stack gets you better cache locality, since elements sit contiguously in memory, which usually means faster real-world performance despite both giving O(1) push and pop in theory. The cost is that a fixed-size array needs resizing logic once it fills up, amortized O(1) if you double the capacity, but an occasional expensive copy.
A linked-list-backed stack avoids the resizing question entirely, every push allocates a new node, but pays for it with worse cache locality (each node can be scattered anywhere in memory) and the per-node overhead of storing a pointer alongside each value. For most interview-scale problems the difference is invisible. For a real production system processing millions of operations per second, cache locality usually wins and the array-backed version is the default choice.
Correct and fast doesn't mean maintainable. I'd flag code that's clever in a way that costs the next reader time to decode, a one-liner doing three things at once when three clear lines would cost nothing at runtime but save real time in every future review. I'd also flag missing error handling around anything that touches the network, disk, or external input, even if the happy path works fine in testing, because production traffic eventually hits the case your test data didn't.
Pick a project where something actually went sideways, not one that finished smoothly on schedule, a smooth project doesn't demonstrate what happens when you're under real pressure. Structure the answer around what made it hard specifically, ambiguous requirements, a technical constraint you didn't anticipate, a teammate leaving mid-project, then what you actually changed about your approach because of it.
The "what you learned" part is where most answers go weak, defaulting to something generic like "communication is important." Be specific: what would you do differently starting that exact project again today, and has that lesson actually shown up in how you've worked since. A lesson you can point to applying later is far more convincing than one you just claim to have learned.
Address it directly and privately with the person first, before escalating to a manager or letting it fester silently, is the answer most interviewers are listening for, and it's also just the right approach in practice. Escalating immediately signals you avoid direct conversations. Saying nothing and letting resentment build signals you avoid conflict entirely, which is its own problem on a team that needs to disagree productively sometimes.
If you have a real, specific example, use it, briefly describe the disagreement, how you approached the conversation, and what changed afterward. A concrete example beats a hypothetical every time, because it shows you've actually done this rather than just knowing the textbook-right answer.
Don't narrate your resume chronologically, that's what the document itself already does. Pick two or three projects that show range, something technically deep, something where you led or influenced beyond your own individual contribution, and be ready to go two or three layers deep on any of them if the interviewer pushes, what specific decision did you make, why, what would you do differently.
A resume walkthrough that stays at the surface level, "I built X using Y," signals you might not have owned the harder decisions on that project. Practice going deep on at least one project until you can answer three consecutive "why" follow-ups without running out of substance.
Medium questions
19It varies more than most large tech companies. Bloomberg hires into specific teams, financial engineering, core infrastructure, the trading desk-facing product groups, and each one has latitude over its own technical bar and question style. Reports describe some teams skipping system design entirely for junior hires, and others weighting it heavily even for new grads.
Ask the recruiter directly which team you're interviewing for and what a typical loop looks like for that group. It's a fair question to ask up front, and a good recruiter will tell you plainly rather than give you the generic loop description.
Most reports say candidates can pick their own language for the OA and phone screens, but the interviewer may steer toward C++ or Java specifics once you're in a live round, especially for infrastructure-facing teams where C++ is the production language. Python shows up more on data-facing and quant-adjacent teams.
If you have a choice, pick the language you can write correct, idiomatic code in without hesitating over syntax, not the one you think sounds impressive. An interviewer watching you fumble basic C++ syntax mid-interview costs you more than picking Python would have.
Sort the array first so duplicates sit next to each other, then backtrack. The trick that actually matters: at each recursion level, skip a candidate element if it's equal to the previous element and the previous element wasn't included in the current path, that's what prevents duplicate subsets without a set for deduplication after the fact.
def subsets_with_dup(nums):
nums.sort()
result = []
def backtrack(start, path):
result.append(path[:])
for i in range(start, len(nums)):
if i > start and nums[i] == nums[i - 1]:
continue
path.append(nums[i])
backtrack(i + 1, path)
path.pop()
backtrack(0, [])
return resultThe core move is a visited set, but the subtlety is distinguishing "visited and fully processed" from "visited and currently on the recursion stack," because that distinction is what tells you if you've found a real cycle versus just revisiting a node you've already finished exploring through a different path.
def has_cycle(graph, node, visiting, visited):
if node in visiting:
return True
if node in visited:
return False
visiting.add(node)
for neighbor in graph[node]:
if has_cycle(graph, neighbor, visiting, visited):
return True
visiting.remove(node)
visited.add(node)
return FalseFor an undirected graph the check simplifies to "have I seen this node before, other than the one I just came from," but for a directed graph (which is what most Bloomberg-context questions actually mean, dependency graphs, transaction ordering) you need the three-color version above.
This is a reported LeetCode-tagged Bloomberg question and it's really a nested-loop comparison problem dressed up with several rules at once: a transaction is invalid if the amount exceeds a threshold, or if there's another transaction with the same name within 60 minutes in a different city. The trick is doing the pairwise city/time check efficiently rather than naively, but for interview purposes O(n²) with early exits is usually accepted if you flag the tradeoff.
def invalid_transactions(transactions):
parsed = [t.split(",") for t in transactions]
invalid = set()
for i, (name, time, amount, city) in enumerate(parsed):
if int(amount) > 1000:
invalid.add(i)
for j, (name2, time2, amount2, city2) in enumerate(parsed):
if i != j and name == name2 and city != city2 and abs(int(time) - int(time2)) <= 60:
invalid.add(i)
return [transactions[i] for i in invalid]State the O(n²) tradeoff up front rather than waiting for the interviewer to point it out. Bloomberg reviewers reportedly reward candidates who name the tradeoff unprompted more than ones who silently write brute force and hope nobody asks.
The common mistake is only checking each node against its immediate children. That misses violations two or more levels down. The correct approach passes a valid range, min and max bounds, down through the recursion, tightening the bound on each side every time you move left or right.
def is_valid_bst(node, low=float("-inf"), high=float("inf")):
if node is None:
return True
if not (low < node.val < high):
return False
return is_valid_bst(node.left, low, node.val) and is_valid_bst(node.right, node.val, high)A quick alternative that's fine to mention: do an inorder traversal and check the resulting sequence is strictly increasing. Some interviewers prefer that framing because it generalizes to "how would you detect duplicates" more naturally.
Direct lookup: map each letter to its index in the 26-length score array and sum. The actual interview value here is in edge cases, not the core logic: does the function need to handle uppercase input, empty strings, or characters outside a-z, and does the interviewer want the maximum score across a list of words rather than a single word's score.
def highest_scoring_word(words, scores):
def word_score(word):
return sum(scores[ord(c.lower()) - ord("a")] for c in word)
return max(words, key=word_score)A plain DFS or BFS traversal that collects matches instead of stopping at the first one. The only wrinkle worth mentioning out loud: does the tree allow duplicate values, and if so should the traversal keep searching subtrees below a match or assume values are unique above a certain level. Ask before assuming.
def find_all(node, target, results=None):
if results is None:
results = []
if node is None:
return results
if node.val == target:
results.append(node)
find_all(node.left, target, results)
find_all(node.right, target, results)
return resultsFirst pass: count frequencies with a hash map, then sort by count. The extension is the actual point of the question, sorting the whole collection is O(n log n) when you only need the top K, and a fixed-size min-heap of size K gets you there in O(n log K), which matters once n is large and K is small.
import heapq
from collections import Counter
def top_k_frequent(words, k):
counts = Counter(words)
heap = []
for word, count in counts.items():
heapq.heappush(heap, (count, word))
if len(heap) > k:
heapq.heappop(heap)
return [word for count, word in sorted(heap, reverse=True)]This progression, brute force sort, then heap optimization, is a common Bloomberg pattern reported across rounds: give an obvious solution first, then push toward the more efficient one once it works.
Start with the order book itself: two sorted structures, one for buy orders and one for sell orders, typically implemented as price-level buckets each holding a FIFO queue of orders at that price (price-time priority is the standard fairness rule most exchanges use). A matching engine pulls the best bid and best ask, matches quantity, and generates trade confirmations when prices cross.
Around that core you need an order gateway that validates and sequences incoming orders (sequencing matters enormously, a race condition in order arrival changes who gets filled), a market data publisher that broadcasts book updates to subscribers, and a persistence layer that logs every order and trade for audit and recovery. The honest answer to "how fast does this need to be" is: as fast as the slowest correct step allows, correctness on ordering can't be traded away for latency.
Token bucket is the standard answer and it's the right one here: each client gets a bucket that refills at a fixed rate, and a request costs one token, which naturally allows short bursts up to the bucket size while enforcing a long-run average rate. A fixed-window counter is simpler to implement but has a known flaw, it allows roughly double the intended rate right at window boundaries if a client bursts at the end of one window and the start of the next.
For a trading-facing API specifically, I'd add a second layer beyond simple rate limiting, per-client circuit breaking that temporarily blocks a client entirely if it's sending malformed or clearly erroneous orders, independent of whether it's within its rate limit. Rate limiting protects capacity. It doesn't protect you from a client sending garbage fast enough to still be technically within budget.
C++ wins when the service sits on a latency-critical path where microseconds matter, market data ingestion, order routing, anything where garbage collection pauses or interpreter overhead would be unacceptable. The cost is development speed and a much larger surface for memory bugs, use-after-free, buffer overruns, that Python simply doesn't have to think about.
Python wins for anything where developer velocity and correctness of business logic matter more than raw throughput, internal tools, data analysis pipelines, glue code between systems, most things that aren't sitting directly in a hot request path. A reasonable rule most teams land on: default to Python unless you have a specific, measured reason the latency or memory characteristics require C++, don't reach for C++ out of habit or prestige.
Passing tests tells you the code does what the tests check, not that the tests check the right things. I'd look at whether the tests cover the actual edge cases a reviewer would worry about, empty inputs, boundary values, concurrent access if that's relevant, or whether they only cover the happy path the author was thinking about while writing the code.
Beyond tests, I'm reading for whether the change is scoped tightly to what it claims to do, whether naming and structure would make sense to someone with zero context six months from now, and whether it introduces a dependency or pattern that's inconsistent with how the rest of the codebase does the same thing. A PR that "works" but quietly diverges from existing conventions creates cleanup debt that's easy to miss in the moment and expensive later.
This one is really testing communication style as much as technical judgment. The strongest version of this answer names the specific bug briefly, states the actual impact if it had shipped, and then focuses more time on how the conversation with the author went than on the bug itself. Reviewers are listening for whether you frame it as "here's a problem I found" versus "here's what's wrong with your code," because the first invites a fix and the second invites defensiveness.
If you don't have a dramatic bug story, a smaller, honest example beats an inflated one. Interviewers can usually tell when a story has been stretched to sound more impressive than it was, and a well-told small example lands better than a vague, oversized one.
Pick a real disagreement, not one where you were obviously right and they were obviously wrong in hindsight, those don't say much about how you handle genuine ambiguity. The strongest answers describe a case where reasonable people could land on either side, and focus on how you made your case, with data or a concrete tradeoff, rather than just repeating your opinion louder.
Be honest about the outcome even if it wasn't what you wanted. Did they change their mind, did you change yours, did you both compromise, or did you disagree and then commit to their decision anyway. Interviewers are specifically listening for whether you can disagree respectfully and still execute afterward, since that's the actual skill that matters day to day on a team, not whether you won the argument.
This question is testing accountability more than judgment. Pick a decision where the reasoning made sense at the time with the information you had, then describe plainly what happened when it didn't pan out, and what you did once you realized it wasn't working. The instinct to soften this into "it wasn't really wrong, just different" undercuts the whole point of the question.
What actually lands well: naming the specific signal that told you the decision was wrong, how quickly you acted on that signal once you saw it, and what changed in how you make similar decisions since. Interviewers care less about the mistake itself and more about whether you course-correct quickly or dig in defensively when the evidence turns against you.
Generic answers about "a strong brand" or "great learning opportunities" fall flat because they'd apply equally to a dozen other companies. A specific answer names something about the actual work, the fact that engineering at Bloomberg sits directly upstream of decisions people make with real money, which raises the bar on correctness and testing in a way that's different from a typical consumer product team, and connects that to a specific gap you're trying to close in your own experience.
If you can, reference something concrete you learned about a specific team or product area during your research rather than talking about Bloomberg in the abstract. It signals you did the homework the generic answer skips.
The useful version of this answer isn't just "I asked clarifying questions," everyone says that. It's describing specifically what you did while the ambiguity was still unresolved, did you make a reasonable assumption and move forward while flagging it explicitly, did you build something small and disposable to validate the requirement before committing to the full build, did you go find the actual stakeholder rather than guessing.
End with what the ambiguity turned out to be and whether your interim approach held up once it was resolved. If your assumption was wrong, say so honestly and describe how much rework it caused, that honesty reads better than pretending you guessed correctly every time.
Give a range grounded in actual research, current market data for the level and location, not a number pulled from what you'd like to earn. Naming a specific researched range signals you've done your homework and gives the recruiter something concrete to work with, rather than an evasive "I'm flexible" that just pushes the negotiation later without any new information.
On other interviews, be honest but brief. You don't owe details about specific companies, but a vague non-answer reads as either overstating your options or hiding something. "I'm in later-stage conversations with a couple of other companies" is enough information without turning it into a negotiating tactic mid-interview.
Hard questions
7This is one of the harder reported questions, and it's testing whether you understand what a language's built-in hash map is actually doing beneath that, not whether you can memorize an implementation. Chaining is the simplest correct approach: an array of buckets, each bucket holding a linked list (or small vector) of key-value pairs that hashed to that index. Insert hashes the key, appends to the bucket's list if the key isn't already there, otherwise updates in place.
template <typename K, typename V>
class SimpleHashMap {
std::vector<std::list<std::pair<K, V>>> buckets;
size_t bucket_count;
size_t hash(const K& key) const {
return std::hash<K>{}(key) % bucket_count;
}
public:
SimpleHashMap(size_t n = 16) : buckets(n), bucket_count(n) {}
void insert(const K& key, const V& value) {
auto& bucket = buckets[hash(key)];
for (auto& kv : bucket) {
if (kv.first == key) { kv.second = value; return; }
}
bucket.emplace_back(key, value);
}
bool find(const K& key, V& out) const {
const auto& bucket = buckets[hash(key)];
for (const auto& kv : bucket) {
if (kv.first == key) { out = kv.second; return true; }
}
return false;
}
};The real conversation happens after the code compiles: what's the load factor threshold for resizing, how do you rehash without stopping the world, and why open addressing might beat chaining for cache locality even though chaining is easier to reason about. Interviewers reportedly probe exactly that tradeoff once the basic version works.
The standard Java approach XORs the upper 32 bits with the lower 32 bits: (int)(value ^ (value >>> 32)). That folds the full 64 bits of information into a 32-bit int instead of just truncating, which would throw away half the entropy and make every value that shares the same lower 32 bits collide.
A good hash function distributes inputs uniformly across the output range, is cheap to compute (you're calling this on every insert and lookup), and is deterministic, same input always produces the same hash within a single run. It's fine to be slow if the input space is small, but for a hash map backing millions of entries, a poorly distributed hash function silently degrades every operation from O(1) to O(n) as buckets collide.
Single-pass tracking of the minimum price seen so far, updated as you scan left to right, checking at each point what profit selling today at the current minimum would yield. Repeat per asset (per row or column depending on how the matrix is laid out).
def max_profit(prices):
min_price = float("inf")
best = 0
for price in prices:
min_price = min(min_price, price)
best = max(best, price - min_price)
return bestThe multi-asset extension is really just "run this per row," but the interesting follow-up is what changes if you're allowed multiple non-overlapping transactions per asset instead of one, that shifts the problem into a small dynamic programming table tracking holding versus not-holding state, and it's worth having that upgrade path ready even if the base question doesn't ask for it.
Time-based sharding is the natural fit here, most queries against historical trade data ask for a specific date range, so partitioning by month or quarter means a typical query only touches one or two shards instead of scanning everything. This also makes archiving old data straightforward, you can move an entire shard to cheaper cold storage once it's outside your active query window.
The tradeoff to name explicitly: sharding by symbol instead of time would make single-symbol queries faster but would fragment date-range queries across every shard, which is the more common access pattern for trade data. Pick the shard key that matches how the data actually gets queried, not the one that sounds more elegant on a whiteboard.
Start with a memory profiler, Valgrind's memcheck or a tool like AddressSanitizer, run against a workload that reproduces the growth, rather than reading code line by line hoping to spot the missing delete. Those tools will point directly at allocation sites whose memory is never freed, which narrows the search from "the whole codebase" to a specific handful of call sites.
Common root causes worth checking specifically: a container that keeps growing because entries are never removed (a cache with no eviction policy is the classic one), a circular reference between shared pointers that prevents reference counts from ever hitting zero, or an exception path that skips a cleanup step a normal return path would have hit. In modern C++, RAII and smart pointers eliminate most manual leak sources, so when a leak does show up in a codebase using them properly, cyclic ownership or an explicit raw new without a matching owner are the first two places I'd check.
This is genuinely testing how you learn under time pressure, not what you learned. A strong answer is specific about the method, did you read source code instead of docs because the docs were thin, did you find one expert and ask targeted questions instead of reading broadly, did you build a small throwaway prototype to test your understanding before committing to the real implementation.
Be honest about what you got wrong along the way. A candidate who claims they picked up an unfamiliar system perfectly on the first attempt is less convincing than one who describes a specific misunderstanding they hit, how they caught it, and what they'd do differently next time to catch it sooner.
The trap in this question is answering with a job title, "senior engineer" or "team lead," which tells the interviewer almost nothing useful. A better answer describes the kind of problems you want to be trusted with by then, more ownership over a system end to end, more influence over technical direction for a team, and connects that specifically to what this particular role would let you build toward.
Avoid overpromising loyalty you can't guarantee, and avoid sounding like the role is just a stepping stone either. The honest middle ground, this role maps onto real growth you're looking for right now, and you'd expect to reassess in a few years like anyone would, reads as more credible than either extreme.
Real-time scenario questions
9This is the "simulate a stock market" question reported across multiple loops, and it's really a max/min stack or two-heap problem wearing a finance costume. A hash map keyed by timestamp gives you O(1) lookup and update of the latest price. Max and min in O(1) needs either two auxiliary max-heaps and min-heaps with lazy deletion, or a pair of monotonic stacks if updates only ever append.
import heapq
class StockPrice:
def __init__(self):
self.prices = {} # timestamp -> price
self.max_heap = [] # (-price, timestamp)
self.min_heap = [] # (price, timestamp)
self.latest_ts = 0
def update(self, timestamp, price):
self.prices[timestamp] = price
self.latest_ts = max(self.latest_ts, timestamp)
heapq.heappush(self.max_heap, (-price, timestamp))
heapq.heappush(self.min_heap, (price, timestamp))
def current(self):
return self.prices[self.latest_ts]
def maximum(self):
while -self.max_heap[0][0] != self.prices.get(self.max_heap[0][1]):
heapq.heappop(self.max_heap)
return -self.max_heap[0][0]The lazy-deletion detail, checking that the heap top still matches the current recorded price before trusting it, is what separates a working answer from one that breaks the moment a price gets corrected after the fact. Bloomberg reviewers reportedly ask exactly that follow-up: what happens if the same timestamp gets updated twice?
An array plus a hash map solves this cleanly. The array gives you O(1) random access for getRandom. The hash map, keyed by value and storing its index in the array, gives you O(1) lookup for delete. The trick for delete is never removing from the middle of the array directly, swap the element you're deleting with the last element, update the swapped element's index in the map, then pop the last element off.
import random
class RandomizedSet:
def __init__(self):
self.data = []
self.index = {}
def insert(self, val):
if val in self.index:
return False
self.index[val] = len(self.data)
self.data.append(val)
return True
def remove(self, val):
if val not in self.index:
return False
idx = self.index[val]
last = self.data[-1]
self.data[idx] = last
self.index[last] = idx
self.data.pop()
del self.index[val]
return True
def get_random(self):
return random.choice(self.data)This is the reported LRU-cache variant from the phone screen round. A doubly linked list plus a hash map gives O(1) access and reordering: the hash map points from URL to its node in the list, and every visit moves that node to the front. When the list exceeds 5 entries, drop the tail.
from collections import OrderedDict
class RecentSites:
def __init__(self, capacity=5):
self.capacity = capacity
self.sites = OrderedDict()
def visit(self, url):
if url in self.sites:
self.sites.move_to_end(url)
else:
self.sites[url] = True
if len(self.sites) > self.capacity:
self.sites.popitem(last=False)
def top_sites(self):
return list(reversed(self.sites))Using OrderedDict is a fair shortcut in an interview once you can explain what it's doing internally, a hash map plus a doubly linked list, exactly the manual version you'd build if asked to implement it from scratch.
This is a read-heavy, write-rare workload, so an in-memory cache with a long TTL or explicit invalidation on write beats a request-per-read design by orders of magnitude. Cache-aside is the simplest correct pattern: check the cache, on a miss go to the source of truth and populate the cache, and invalidate explicitly whenever the underlying reference data changes rather than relying purely on TTL expiry.
The interesting design decision is where the cache lives. Local, in-process caching on each service instance is fastest but risks staleness across instances after an update. A shared cache tier keeps everyone consistent but adds a network hop. For reference data that changes maybe a few times a day, I'd lean toward local caches with a lightweight invalidation broadcast (a pub/sub message telling every instance "reload this key") rather than paying a network round trip on every single read.
Start Understanding what it's supposed to do and confirming it's actually correct before optimizing anything, a fast wrong answer is worse than a slow right one. Then look for the obvious complexity issues first: nested loops doing work that could be a single pass with a hash map, repeated work inside a loop that could be hoisted out, or a data structure choice that doesn't match the access pattern, like a list being searched linearly when a set or dict would give O(1) lookup.
Say what you'd measure before you'd change anything. In a real production system I wouldn't guess at the bottleneck, I'd profile it. In an interview, without profiling tools available, name the complexity of each section out loud and point at the one that's asymptotically worst, that's usually where the actual slowness lives, not in constant-factor details.
This is the reported "stock trading data design problem" from the manager round, and it's testing whether you can lead a design conversation as a peer rather than answer a quiz. Position tracking is fundamentally an event-sourced ledger: every trade is an immutable event, and current position for any desk and instrument is derived by replaying or aggregating events rather than mutating a single row directly, which gives you a full audit trail for free and makes reconciliation against another system far simpler.
The manager-round version of this question rewards asking sharp scoping questions early, real time meaning what latency exactly, how many desks, does "position" need to reflect pending orders or only settled trades, more than it rewards jumping straight into component diagrams. Show that you'd scope the actual problem with a stakeholder before building the wrong thing efficiently.
The instinct to reach for a general-purpose message queue with heavy durability guarantees is usually wrong at this scale, every extra fsync or acknowledgment round trip costs microseconds you don't have. A more realistic design uses a ring buffer or lock-free queue in shared memory for the hot path, with consumers reading at their own pace and a separate, slower path handling persistence and replay asynchronously.
Backpressure is the part candidates forget. If a downstream consumer falls behind, you need an explicit policy, drop the oldest data, drop the newest, or slow the producer, because "just buffer everything" turns a slow consumer into an unbounded memory leak within seconds at these volumes. I'd also flag that "without losing data" and "without adding latency" are in tension, and a good answer names that tension directly instead of promising both for free.
The core problem is that a newly-connecting client needs the current state of every instrument, not just future updates, otherwise it's missing context for anything that changed before it subscribed. The standard pattern is a snapshot-plus-delta model: on connect, send a full snapshot of current prices, then stream incremental updates from that point forward, tagged with a sequence number so the client can detect if it missed one.
Sequence numbers matter more than people initially expect. Without them, a client that briefly drops and reconnects has no way to know whether it missed updates in between, and silently trading on stale data is a much worse failure mode than an obvious disconnect. A gap in sequence numbers should trigger the client to request a fresh snapshot rather than assume it's still in sync.
Reconciliation is fundamentally a diffing problem at scale, and the design question underneath it is how you compare two large datasets without pulling both entirely into memory or scanning both in full every time. A common approach is periodic checkpoint hashing, both systems compute a hash of their state at fixed intervals, and comparing hashes is cheap. When hashes disagree, only then do you do the expensive full comparison to find exactly where the drift happened.
The part worth surfacing unprompted: reconciliation systems need to be more paranoid about false negatives than false positives. A false positive (flagging a match as a mismatch) wastes an analyst's time investigating something that's actually fine. A false negative (missing a real drift) means bad data sits undetected, which in a financial context can compound. Design the detection threshold accordingly, biased toward catching more, not toward reducing noise.
How to prepare for a Bloomberg interview
Practice medium-difficulty problems on arrays, hash maps, trees, and heaps until you can talk through the approach out loud while writing correct code, that's the skill Bloomberg interviewers reportedly weight as heavily as the final answer. Then push past the base version of each problem: once your solution to a stock-price or top-K problem works, ask yourself the follow-up an interviewer would ask, what if this needs to run concurrently, what if the input is a live stream instead of a fixed array, and practice answering that unprompted.
For system design, resist the urge to memorize a generic "how to design a web app" template. Bloomberg's reported design questions are consistently finance-flavored, an order book, a market data feed, a reconciliation system, so practice framing latency, consistency, and backpressure tradeoffs specifically for that context rather than a generic scaling story. And prepare the "why Bloomberg" answer early. It's not a throwaway question here, reports describe it coming up in nearly every single round, from the recruiter screen through the HR call.
Get the reps in before the real thing
LastRoundAI's mock interview mode runs live coding rounds with real-time follow-up questions in your browser, including finance-flavored system design scenarios like the ones reported in Bloomberg loops. The free plan includes 15 credits a month that reset monthly rather than piling up unused, and Starter is $29/mo (or $19/mo on the 3-month plan) if a handful of sessions isn't enough runway before your loop starts.
Once your answers hold up under a follow-up, Auto-Apply queues tailored applications for your review, 10 a month on the free plan, up to 400 a month on the Ultimate plan, and nothing goes out until you approve it.
Questions about either product go to contact@lastroundai.com.
How this list was built
Worth being straight about where these questions come from, because plenty of pages in this category are not. The set was compiled from a research pass across official documentation, vendor release notes, published engineering writing and public discussion of hiring processes, then cross-checked against the current version of each technology so nothing here describes behaviour that has since changed.
What that means in practice: these are the questions the material supports as reasonable and current for this role, not a transcript of any one company's loop. We have not sat in on your interview and we are not going to claim we have. Treat the list as well-sourced preparation rather than a leaked question bank, and expect your panel to phrase things their own way.
If you spot something out of date, tell us at contact@lastroundai.com and we will fix it.
Frequently asked questions
How technical is the Bloomberg interview?
Technical enough that hand-waving fails. For engineering roles expect data structures, system design and questions about correctness under concurrency or scale. For analyst roles expect quantitative reasoning and comfort explaining a number you produced.
Does Bloomberg ask brainteasers or probability questions?
Quantitative roles frequently do; general technology roles less often than their reputation suggests. If the role touches trading or research, probability and expected-value reasoning are fair game and worth rehearsing out loud.
What behavioural questions come up at Bloomberg?
Ownership under pressure, handling a mistake with financial or client consequence, and working with people who disagree with you. Regulated environments care about judgement and escalation, so answers that show you knew when to raise something land well.
How should I prepare for the final round at Bloomberg?
Know the business line you are joining, not just the firm. Candidates who can name what the desk or team actually does, and ask a specific question about it, separate themselves quickly at this stage.
What does the Bloomberg interview process look like?
Typically a recruiter screen, one or two technical or case rounds, and a final round with senior staff. Technology roles add a coding assessment; front-office roles lean harder on market awareness and behavioural depth. Expect the bar on precision to be higher than at a typical product company.
