For an Uber software engineer interview, prepare to solve a problem and to explain how the solution behaves after the first happy-path call. A ranking service can return the right winner once and still fail when a score falls, two entities tie, or concurrent callers increment the same record.
This guide distinguishes algorithm practice, machine coding and system design. It includes a complete original Python service with two implemented extensions and meaningful checks. The exercise is teaching material; it is not an unpublished Uber prompt or evidence about its scoring system.
![]()
What is confirmed about the interview process?
Uber’s current general hiring page describes talent and hiring-manager conversations, a collaborative technical interview for technical roles, a role-dependent exercise and team interviews. It emphasizes clear communication and job-related problem solving. It also says the process varies by role. That supports preparing for practical work; it does not establish a universal round count, OA vendor or machine-coding duration.
Before planning, ask your recruiter for your actual stages, coding environment, allowed language, documentation and AI rules, and whether implementation or architecture is expected. An assessment invitation outranks a generic preparation page. Do not assume AI assistance is allowed because you use it while studying.
This page’s question counts and round labels describe PracHub’s collection. They are not probabilities of receiving a question or a required Uber schedule. For active practice, use Design a Real-Time Top-K Ranking System. The verified Uber-tagged record asks for score updates, removals, deterministic ordering and follow-ups. Its large-scale requirements go beyond the bounded baseline below.
Prepare three kinds of evidence
| Practice lane | What to demonstrate | A useful review question |
|---|---|---|
| Algorithms | Clarified contract, correct invariant, appropriate complexity | Which smallest input breaks the first idea? |
| Machine coding | Runnable API, state transitions, validation, tests | What changes on the second call? |
| System design | Load assumptions, consistency, failures, ownership | Which guarantee survives a partial failure? |
| Project discussion | Your decision, alternatives, collaboration and result | What would you change with today’s information? |
Graphs, trees, maps, heaps, intervals and binary search make a useful coverage set. Avoid declaring any one topic a guaranteed high-frequency Uber question. For a dependency-ordering problem, explain directed edges, include isolated nodes, reject unknown dependencies under your chosen contract, and detect a cycle when fewer than all vertices are emitted. State O(V+E) for a properly represented topological traversal.
For machine coding, start with the public methods and write a short operation trace before classes. Extra patterns and inheritance do not compensate for stale state. For system design, give each component a concrete responsibility, then show where a write becomes visible and what happens if the reply disappears.
Worked machine-coding contract: a bounded ranking service
Original mock. A single process holds up to 10000 active entities. update(id, score) inserts or replaces an absolute score; remove(id) returns whether an entity existed; top_k(k) returns a new list ordered by descending score and ascending ID. Scores are integers from −10^9 to 10^9. IDs are nonempty strings of at most 100 characters, compared using Python string ordering. Their canonicalization belongs upstream.
Zero K returns an empty list; K above the population returns everyone; negative K and booleans are invalid. Negative and zero scores are valid. Invalid input and a capacity rejection leave the current state unchanged. This is active-population capacity, not a promise about the total memory used by arbitrary payloads.
The invariant is precise: every map entry (id, score) has exactly one ordered key (-score, id), and no ordered key exists without its map entry. Updating must remove the old key before inserting the new one. A descending-score heap alone cannot cheaply provide arbitrary replacement, removal and a reusable ordered Top-K view without additional machinery.
| Operation | Active scores afterward | Top two |
|---|---|---|
| B=90, A=90, C=70 | A90, B90, C70 | A90, B90 |
| Replace A with 60 | A60, B90, C70 | B90, C70 |
| Replace C with 95 | A60, B90, C95 | C95, B90 |
| Remove B | A60, C95 | C95, A60 |
| Increment A by 40 | A100, C95 | A100, C95 |

Complete implementation and its real costs
The following code uses a dictionary plus a sorted Python list and one reentrant lock. Query work is proportional to the returned slice; it does not sort or scan all entities for each query. Writes are still O(n) because list insertion and deletion move elements. Use this for an agreed modest read-heavy baseline, not as a finished solution to millions of frequent updates.
from bisect import bisect_left, insort
from threading import RLock
class Ranking:
def __init__(self, capacity=10000):
if type(capacity) is not int or capacity < 1:
raise ValueError('capacity must be a positive integer')
self._capacity = capacity
self._scores = {}
self._order = []
self._lock = RLock()
@staticmethod
def _id(entity):
if not isinstance(entity, str) or not entity or len(entity) > 100:
raise ValueError('entity must be a nonempty string of at most 100 characters')
@staticmethod
def _score(score):
if type(score) is not int or abs(score) > 10**9:
raise ValueError('score must be an integer in [-1e9, 1e9]')
def _put(self, entity, score):
if entity not in self._scores and len(self._scores) == self._capacity:
raise ValueError('capacity reached')
if entity in self._scores:
old = (-self._scores[entity], entity)
self._order.pop(bisect_left(self._order, old))
insort(self._order, (-score, entity))
self._scores[entity] = score
def update(self, entity, new_score):
self._id(entity)
self._score(new_score)
with self._lock:
self._put(entity, new_score)
def remove(self, entity):
self._id(entity)
with self._lock:
if entity not in self._scores:
return False
score = self._scores.pop(entity)
self._order.pop(bisect_left(self._order, (-score, entity)))
return True
def top_k(self, k):
if type(k) is not int or k < 0:
raise ValueError('k must be a nonnegative integer')
with self._lock:
return [(entity, -score) for score, entity in self._order[:k]]
def increment(self, entity, delta):
self._id(entity)
self._score(delta)
with self._lock:
new_score = self._scores.get(entity, 0) + delta
self._score(new_score)
self._put(entity, new_score)
return new_score
def apply_batch(self, records):
results = []
for index, record in enumerate(records):
try:
if not isinstance(record, (list, tuple)) or len(record) != 2:
raise ValueError('record must contain entity and score')
self.update(*record)
results.append((index, 'ok'))
except ValueError as error:
results.append((index, str(error)))
return results
The lock spans both structures and every query snapshot. Returning fresh tuples and a fresh list prevents the caller from mutating internal state. It also spans the read-modify-write of increment. Locking only list insertion would leave a reader able to observe a map/list mismatch. Python’s bisect documentation warns about concurrent mutation; it also distinguishes logarithmic search from linear insertion.
update, remove and increment have O(n) worst-case list movement; top_k takes O(min(k,n)) time and result memory. Stored state is O(n), with ID length bounded here. These are algorithmic costs, not measured service throughput. A lock serializes competing operations, and neither the GIL nor a fast dictionary lookup establishes a latency target.
Input validation protects ordinary contract errors. A fatal allocation failure or process termination is outside this in-memory service’s recovery guarantee. There is no durable log, network endpoint, authorization layer or replicated state. State disappears when the process exits.
Two extensions you can actually run
Atomic increments. Suppose two callers see A=60 and independently calculate 61. Two later absolute updates produce 61, losing an increment. increment('A', 1) reads, validates and replaces under one lock. An unknown entity begins at zero; an overflow or a full-capacity insertion is rejected before mutation. Increments are not retry-idempotent: repeating a request adds again.
Per-record batches. Given [(A,5), (broken,), (B,True), (B,7), (A,9)], the method reports success for indices 0, 3 and 4, rejects the malformed tuple and boolean, and finishes with A=9, B=7. It preserves valid progress rather than treating one bad record as a reason to discard every later item. The input records remain unchanged.
The batch is intentionally not atomic. Another caller may interleave between records, and a reader may see partial progress. It expects a normal iterable; an iterator that itself raises terminates iteration. A huge batch also grows its result list. A production boundary needs record and byte limits, bounded response handling and explicit retry semantics.
For externally delivered events, add a per-entity monotonic version from a defined authority. Reject stale versions; compare payloads for equal-version conflicts; preserve deletion tombstones long enough to prevent resurrection. A local arrival counter cannot reconstruct source order. Persist the version and state change together. These versioned-event rules are a design follow-up, not implemented by apply_batch.
Checks that distinguish correctness from a plausible demo
The local suite passed 16471 checks, including 4500 generated operations across 30 seeds. Its oracle stores plain scores and fully sorts them independently; it does not call the service’s ordered-list helper. After each generated update, removal or increment, it compares both the query and paired-structure state.
Focused cases cover empty rankings, zero and oversized K, ties, score increases and decreases, repeat removal, negative scores, capacity, invalid types and overflow. The mixed batch checks error positions, successful final state and unchanged caller input. Four actual threads each performed 250 increments against the same entity; the final score was 1000.
That concurrency test exercises one shared process. It does not prove every possible schedule, distributed linearizability, persistence, request deduplication or high-load performance. Re-run a smaller focused regression while editing; the larger seeded suite helps find state-sequence bugs but does not replace an explanation of the invariant.

What changes when updates become frequent or distributed?
For large N and frequent writes, replace list movement with an ordered tree or another structure whose operations match the workload. Keep a lookup map and deterministic composite key. An O(log n) update plus O(k) ordered traversal is a useful target when the selected structure supports it; justify the actual library behavior rather than assuming every tree exposes rank queries.
Sharding changes the contract. If each entity belongs to exactly one shard and all shards represent the same logical snapshot, merging each shard’s local Top-K can recover the global Top-K: an entity below K on its own shard already has at least K local entities ahead of it. Duplicate placement, incomparable score definitions or different snapshot times break those assumptions.
Independent shard queries can produce a result that never existed at one instant. Choose a coordinated versioned snapshot, a single authoritative ranking index, or explicitly eventual results. Describe the availability and latency cost. A missing shard should not silently yield a supposedly complete ranking; return partial status or fail according to the agreed API.
For a ride or delivery product example, clarify whether the score measures quality, availability, distance or something else. Avoid using the toy score as a real dispatch policy. Safety, eligibility, fairness, location freshness and business constraints can change both filtering and ranking. Filter authorization and eligibility before deciding what result the requester may see.
A preparation plan that produces reviewable work
Use the following ten-session plan if you already know the fundamentals. Expand sessions where your mock exposes a real gap rather than treating ten as a universal preparation deadline.
| Sessions | Work | Evidence to keep |
|---|---|---|
| 1–2 | Graph and tree tasks, including cycles and disconnected inputs | A runnable solution and missed assumptions |
| 3–4 | Maps, heaps, intervals and search with changed constraints | A complexity explanation plus counterexample |
| 5–6 | Implement ranking from a blank file; then add extensions | Tests, operation trace and invariant |
| 7–8 | Design a ranking or delivery service with failure handling | Data model, visibility point and recovery trace |
| 9 | Explain a real project and a disagreement | Your decision, alternatives and measured result |
| 10 | Timed mixed mock and review | One specific next practice action |
Early-career practice should establish correctness, runnable code and clear reasoning. For a mid-level role, add changing requirements, concurrency boundaries and maintenance decisions. For senior scope, explain cross-team ownership, migration, failure containment and which guarantee you deliberately relax. These are preparation recommendations, not a private Uber leveling matrix.
For project discussions, distinguish your contribution from the team’s. State the initial condition, competing options, the colleague who challenged an assumption, the decision and the observed outcome. If a metric improved, include its denominator and period; do not claim causality from a before/after chart alone. A result that disappointed you can still support a precise discussion of judgment.
Before a mock ends, run the changed-score example again, explain why the list solution has linear writes, and name the first production requirement it does not meet. A short accurate limit is more useful than calling a small in-memory class production-ready.
Frequently asked questions
What rounds does Uber use for software engineers?
Uber’s current general hiring page describes talent and hiring-manager conversations, a technical interview for technical roles, a role-dependent exercise and team interviews. It does not establish one fixed SWE loop. Confirm round names, timing, implementation format and tools with your recruiter.
How is machine coding different from an algorithm interview?
An algorithm interview concentrates on solving a bounded computational problem. Machine coding also asks you to expose a usable API, maintain state across calls, define invariants and test changes. The ranking service here makes these differences concrete without claiming every Uber team uses the same exercise.
Is this ranking service fast enough for millions of updates?
No. It avoids scanning all entities during a Top-K query, but sorted-list insertion and deletion shift elements and cost O(n). It is a bounded teaching baseline for a modest read-heavy workload. A high-update design needs a more suitable ordered structure and measured contention.
Does the batch extension handle retries and stale events?
It validates and applies records individually, reports errors and continues. It does not provide event deduplication, version checks, all-or-nothing batch transactions or rollback. Repeated absolute assignments are harmless only when no newer value intervenes; stale replay can overwrite that newer value.
How should preparation change for senior candidates?
Keep runnable code and edge-case tests, then add consistency, capacity, recovery, operational ownership and migration trade-offs. Prepare a real project story with your decision, alternatives, colleagues’ contributions and measured result. These are editorial practice recommendations, not Uber’s private leveling rubric.
Sources and scope
- Uber Careers: How we hire: current general hiring stages and role variation.
- Python: bisect: sorted insertion, complexity and concurrent mutation limits.
- Python: threading: shared-process synchronization primitives.
Checked October 9, 2026. The ranking service, operation trace and tests are original local practice material. The linked PracHub record was read in full; its company tag is practice context, not official confirmation that a particular candidate will receive it.