← Uber Interview Insights

Uber·Software Engineer·Onsite - System Design / Architecture·Senior

SeniorPrefer not to say
Apr 2026

Summary

Uber system design round for a software engineer position. The whole session was basically one deep problem: build a thread-safe token bucket rate limiter, then defend every design choice under concurrent load. Tougher than I expected for what sounds like a contained problem.

Questions Asked (4)

Q1

Design and implement a thread-safe rate limiter using the token bucket algorithm. It needs to support concurrent access from many worker threads and expose an API that allows consuming one or more tokens atomically.

System DesignTechnical Trade-offsAlgorithms & Data Structures
Author's notes

Spent the first few minutes just talking through the token bucket model before touching code, which I think was the right call.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying requirements (rate, burst capacity, fairness, blocking vs non-blocking) and then design a token bucket with a mutex or lock-free approach. Implement a class with a method to atomically consume tokens, using a background refill thread or lazy refill on each request. Discuss trade-offs between precision, performance, and complexity.

Pro tip: Mention that you would use a monotonic clock for refill timing to avoid issues with system clock adjustments, and consider using a lock-free atomic approach for high contention scenarios.

1. Clarify Requirements

Ask about expected rate, burst size, number of threads, whether blocking is allowed, and if fairness (FIFO) is needed. This shapes the design.

2. Design Data Structures

Define the token bucket with capacity, current tokens, refill rate, and last refill timestamp. Choose synchronization primitives (mutex, atomic, condition variable).

3. Implement Atomic Consumption

Write a method that locks, refills tokens based on elapsed time, checks if enough tokens, deducts, and returns success/failure. Ensure atomicity.

4. Handle Concurrency and Blocking

If blocking is required, use condition variables to wait for tokens. Otherwise, return immediately. Discuss fairness and potential starvation.

5. Analyze Trade-offs and Optimizations

Compare mutex vs lock-free, lazy vs background refill, and discuss performance under high contention. Mention possible optimizations like sharding or using atomic CAS.

Key Points to Mention

  • Token bucket algorithm: tokens added at fixed rate, bucket has max capacity, each request consumes tokens.
  • Thread safety: use mutex or atomic operations; ensure atomic check-and-decrement.
  • Refill strategy: lazy refill on each request vs background thread; trade-offs in precision and overhead.
  • Blocking vs non-blocking API: condition variables for blocking, immediate return for non-blocking.
  • Fairness: FIFO queue for waiting threads to prevent starvation.
  • Performance considerations: contention, lock granularity, lock-free alternatives, and clock choice (monotonic).

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q2

How would you reduce lock contention in the rate limiter under very high concurrency? Compare a global lock, a lock-free CAS approach, and sharding across multiple buckets.

System DesignTechnical Trade-offs
Author's notes

This is where the conversation got interesting.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by framing the problem: under high concurrency, a rate limiter's lock becomes a bottleneck, so we need to reduce contention while preserving correctness. Then compare the three approaches—global lock, lock-free CAS, and sharding—by analyzing their trade-offs in throughput, latency, complexity, and accuracy, and conclude with a recommendation (e.g., sharding with per-shard locks or CAS) tailored to Uber's scale.

Pro tip: Mention that sharding can be combined with CAS per shard to get the best of both worlds, and that the choice depends on the required accuracy (e.g., strict global limit vs. approximate per-shard limit) and the cost of coordination.

1. Clarify requirements and constraints

Ask about the required accuracy (strict global limit vs. approximate), expected QPS, latency SLA, and whether the limiter is per-user, per-IP, or global. This sets the context for trade-offs.

2. Analyze the global lock approach

Explain that a single mutex serializes all requests, causing contention and limiting throughput to the lock's critical section. It's simple and accurate but doesn't scale under high concurrency.

3. Analyze the lock-free CAS approach

Describe using atomic compare-and-swap on a shared counter. It avoids blocking but can suffer from CAS retry storms under high contention, wasting CPU and increasing latency.

4. Analyze sharding across multiple buckets

Propose partitioning the key space (e.g., by user ID) into N shards, each with its own lock or CAS. This reduces contention by a factor of N but may allow up to N times the limit if not coordinated.

5. Compare and recommend

Summarize trade-offs: global lock (simple, accurate, poor scalability), CAS (better scalability but retry overhead), sharding (high scalability, approximate). Recommend a hybrid (e.g., sharded CAS) and mention techniques like token bucket with lazy refill to reduce writes.

Key Points to Mention

  • Lock contention causes serialization and limits throughput; measure with metrics like lock wait time.
  • Global lock: simple and accurate but poor scalability; can use read-write locks if reads dominate.
  • Lock-free CAS: avoids blocking but can cause CPU waste due to retries; consider backoff strategies.
  • Sharding: reduces contention linearly with number of shards; trade-off is approximate global limit.
  • Hybrid approach: shard the state and use CAS per shard to minimize contention while maintaining per-shard accuracy.
  • Alternative optimizations: use token bucket with lazy refill, or approximate algorithms like sliding window with Redis.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q3

How would you extend this design to support per-user or per-key rate limiting, and how would you manage memory for a large, sparse key space?

System DesignAPI & Integrations
Author's notes

Went straight to a concurrent hash map keyed by user ID.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the current design and requirements (e.g., rate limit granularity, accuracy, scale). Then propose a distributed rate limiting architecture using a key-value store like Redis with appropriate data structures and sharding, and discuss memory management techniques such as sharding, TTLs, and probabilistic data structures for sparse keys.

Pro tip: Mention that for per-key rate limiting, you can use a hash-based approach where each key maps to a counter with a TTL, and for sparse keys, consider using a probabilistic data structure like a count-min sketch to reduce memory, but be transparent about the trade-off in accuracy.

1. Clarify Requirements and Constraints

Ask about the expected scale (number of users/keys, request rate), accuracy requirements, and latency constraints. This will guide the choice of data store and algorithm.

2. Design Per-User/Per-Key Rate Limiting

Propose using a distributed counter store (e.g., Redis) with keys like `rate_limit:{user_id}:{window}`. Use atomic operations (INCR, EXPIRE) to implement sliding window or token bucket algorithms.

3. Address Scalability and Sharding

Discuss sharding the key space across multiple Redis instances (e.g., consistent hashing) to distribute load and avoid hotspots. Mention using a proxy or client-side sharding.

4. Manage Memory for Sparse Key Space

For large, sparse key spaces, use TTLs to expire inactive keys, and consider probabilistic data structures (e.g., count-min sketch) or approximate counting to reduce memory footprint, accepting some inaccuracy.

5. Handle Failures and Consistency

Discuss trade-offs between consistency and availability (e.g., using Redis with replication, or a eventually consistent store). Mention fallback strategies like local rate limiting if the central store is unavailable.

Key Points to Mention

  • Use of Redis or similar in-memory data store for low-latency counters
  • Sliding window vs. fixed window vs. token bucket algorithms and their trade-offs
  • Sharding and consistent hashing to distribute keys and scale horizontally
  • TTL and expiration policies to automatically clean up inactive keys
  • Probabilistic data structures (e.g., count-min sketch) for memory-efficient approximate counting
  • Handling hot keys and ensuring atomicity with Lua scripts or transactions

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q4

What are the trade-offs between a token bucket, a sliding window counter, and a leaky bucket rate limiter?

Technical Trade-offsSystem Design
Author's notes

Knew this one reasonably well.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by defining each algorithm's core mechanism and then compare them across dimensions like burst handling, memory usage, precision, and implementation complexity. Use a concrete example (e.g., API rate limiting at Uber) to illustrate trade-offs and conclude with when to choose each.

Pro tip: Mention that token bucket and leaky bucket are duals: token bucket allows bursts up to bucket size, while leaky bucket smooths output at a constant rate. This shows deep understanding and helps you stand out.

1. Define each algorithm

Briefly explain how token bucket, sliding window counter, and leaky bucket work, focusing on their core mechanics.

2. Compare on key dimensions

Analyze trade-offs in terms of burst handling, memory footprint, accuracy, and implementation complexity.

3. Discuss practical implications

Relate each algorithm to real-world scenarios, such as API rate limiting, DDoS protection, or traffic shaping.

4. Recommend based on requirements

Conclude with guidance on when to use each algorithm, considering factors like need for burst tolerance, precision, and resource constraints.

Key Points to Mention

  • Token bucket allows bursts up to bucket capacity, while leaky bucket enforces a smooth constant output rate.
  • Sliding window counter provides more accurate rate limiting than fixed windows but requires more memory and computation.
  • Memory usage: token bucket and leaky bucket use O(1) space per key, while sliding window counter may use O(window size) or O(1) with approximations.
  • Implementation complexity: token bucket and leaky bucket are simple to implement, while sliding window counter can be more complex, especially with distributed systems.
  • Distributed system considerations: token bucket and leaky bucket can be implemented with a centralized store, but sliding window counter may need synchronization across nodes.
  • Trade-off between precision and performance: sliding window counter offers better precision at the cost of higher resource usage.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.