cswork / libraryOpen practice workspace

Interview guides · Uber

Uber Software Engineer Interview Guide 2026

Prepare for Uber SWE interviews with a tested Top-K ranking class, score changes, batch errors, atomic increments, design follow-ups and a focused study plan.

Interviewing at Uber

  • Practice bank: 187+ questions
  • Rounds: 3
  • Typical prep: 2–4 weeks
  • Interview reports: 28

Interview rounds

RoundQuestions
Online Assessment31
Technical Screen65
Onsite91

How hard is the Uber Software Engineer interview?

DifficultyShareQuestions
Easy2%4
Medium74%139
Hard24%44

What Uber actually tests for

TopicShareQuestions
Coding & Algorithms69%129
System Design19%35
Software Engineering Fundamentals8%15
Behavioral & Leadership4%7
Analytics & Experimentation1%1

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.

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 laneWhat to demonstrateA useful review question
AlgorithmsClarified contract, correct invariant, appropriate complexityWhich smallest input breaks the first idea?
Machine codingRunnable API, state transitions, validation, testsWhat changes on the second call?
System designLoad assumptions, consistency, failures, ownershipWhich guarantee survives a partial failure?
Project discussionYour decision, alternatives, collaboration and resultWhat 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.

OperationActive scores afterwardTop two
B=90, A=90, C=70A90, B90, C70A90, B90
Replace A with 60A60, B90, C70B90, C70
Replace C with 95A60, B90, C95C95, B90
Remove BA60, C95C95, A60
Increment A by 40A100, C95A100, 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.

SessionsWorkEvidence to keep
1–2Graph and tree tasks, including cycles and disconnected inputsA runnable solution and missed assumptions
3–4Maps, heaps, intervals and search with changed constraintsA complexity explanation plus counterexample
5–6Implement ranking from a blank file; then add extensionsTests, operation trace and invariant
7–8Design a ranking or delivery service with failure handlingData model, visibility point and recovery trace
9Explain a real project and a disagreementYour decision, alternatives and measured result
10Timed mixed mock and reviewOne 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.

Sources and scope

Checked October 9, 2026. The ranking service, operation trace and tests are original local practice material.