← Uber Interview Insights

Uber·Software Engineer·Technical Phone Screen·Intermediate

IntermediatePrefer not to say
Jul 2026Remote

Summary

Uber SWE interview with an OOD design question that looked simple at first glance but had a pretty sharp follow-up about scaling. The core problem was manageable but the million-inputs-per-second angle is where things got interesting.

Questions Asked (2)

Q1

Design an expiring counter class that tracks how many unexpired copies of each element are stored, with methods to add elements at a given timestamp, query the count for a specific element, and query the total count across all elements. Expiration is based on a sliding window passed at initialization.

Algorithms & Data StructuresSystem DesignTechnical Trade-offs
Author's notes

My first instinct was to use a deque per element and just pop from the front when things expire.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Clarify the requirements and constraints, then propose a design using a hash map to track per-element queues of timestamps and a global queue for total count. Explain how to lazily remove expired entries during add and query operations, and analyze the time and space complexity.

Pro tip: Mention that lazy deletion avoids scanning all elements on every operation, and discuss how to handle concurrent access if the system is multi-threaded, showing awareness of real-world production concerns.

1. Clarify Requirements and Constraints

Ask about expected operations per second, memory limits, thread safety, and whether timestamps are monotonically increasing. This ensures the design meets actual needs.

2. Choose Data Structures

Use a hash map from element to a queue of timestamps for per-element counts, and a global queue of (element, timestamp) for total count. This allows O(1) amortized operations.

3. Define Expiration Logic

On each add or query, remove expired entries from the front of the queues based on the sliding window. This lazy approach avoids periodic full scans.

4. Implement Operations

For add: append timestamp to both queues and increment counts. For query(element): clean expired entries for that element, then return its count. For total: clean global queue, then return total count.

5. Analyze Complexity and Trade-offs

Discuss time complexity (amortized O(1) per operation) and space complexity (O(n) where n is number of unexpired elements). Mention alternatives like using a balanced BST for range queries if timestamps are not monotonic.

Key Points to Mention

  • Use of hash map and queues for efficient per-element and total counts
  • Lazy deletion of expired entries to avoid scanning all elements
  • Handling of non-monotonic timestamps and potential need for sorted structures
  • Thread safety considerations and possible locking strategies
  • Amortized time complexity analysis and worst-case scenarios
  • Memory management and cleanup of empty queues to prevent leaks

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

Q2

Follow-up: if this counter receives around one million events per second, how would you modify your design to handle that load efficiently?

System DesignTechnical Trade-offs
Author's notes

This is where I fumbled a bit.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by acknowledging that 1M events/sec is a massive scale requiring a fundamental shift from a single counter to a distributed, partitioned system. Focus on partitioning the event stream by key (e.g., user ID or event type) to parallelize counting, then aggregate results asynchronously. Emphasize trade-offs between accuracy, latency, and cost, and propose a layered architecture with buffering, stream processing, and a scalable storage layer.

Pro tip: Mention that at Uber's scale, exact real-time counts are often unnecessary; approximate counting with probabilistic data structures (like HyperLogLog) or windowed aggregations can drastically reduce resource usage while meeting business needs. Also, highlight the importance of backpressure and idempotency to handle bursts and retries.

1. Clarify requirements and constraints

Ask about the required accuracy (exact vs approximate), latency (real-time vs batch), and query patterns (e.g., per-user, global). This determines the appropriate trade-offs and technology choices.

2. Partition the event stream

Shard incoming events by a key (e.g., user ID, event type) to distribute load across multiple counter instances. Use a consistent hashing scheme to ensure even distribution and scalability.

3. Introduce a buffering and processing layer

Place a high-throughput message queue (e.g., Kafka) between producers and counters to absorb bursts and decouple ingestion from processing. Use stream processing frameworks (e.g., Flink, Spark Streaming) to perform windowed aggregations.

4. Design scalable storage and aggregation

Store per-partition counts in a distributed database (e.g., Cassandra, Redis) and periodically aggregate them into a global count. For approximate counts, use probabilistic data structures like HyperLogLog.

5. Address reliability and monitoring

Implement idempotent processing, backpressure, and dead-letter queues to handle failures. Monitor throughput, latency, and accuracy, and set up alerts for anomalies.

Key Points to Mention

  • Partitioning/sharding to parallelize counting and avoid single-point bottlenecks
  • Use of a distributed message queue (e.g., Kafka) for buffering and decoupling
  • Stream processing with windowed aggregations (e.g., tumbling windows) to compute counts incrementally
  • Trade-offs between exact and approximate counting (e.g., HyperLogLog for cardinality estimation)
  • Scalable storage solutions (e.g., Redis for fast increments, Cassandra for durability)
  • Idempotency and backpressure to ensure correctness under retries and load spikes

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