359. Logger Rate Limiter
easyAsked at UberDesign a logger that accepts messages once per 10 seconds — the LeetCode anchor for 'design a rate limiter' interview rounds. Uber asks this to test hash-map state, monotonic timestamps, and how you'd extend to per-key sliding windows.
By Alex Chen, Founder, InterviewChamp.AI · Last verified
Source citations
Public interview reports confirming this problem appears in Uber loops.
- Glassdoor (2026-Q1)— Uber backend onsite reports include 'design a rate limiter' rounds where LC 359 is the canonical anchor.
- Blind (2025-11)— Recurring in Uber API-platform interviews — extends naturally to sliding-window and token-bucket follow-ups.
Problem
Design a logger system that receives a stream of messages along with their timestamps. Each unique message should only be printed at most every 10 seconds (i.e., a message printed at timestamp t will prevent other identical messages from being printed until timestamp t + 10). Implement Logger: Logger() initializes the logger; shouldPrintMessage(timestamp, message) returns true if the message should be printed in the given timestamp, otherwise returns false.
Constraints
0 <= timestamp <= 10^9Every timestamp will be passed in non-decreasing order (chronological order).1 <= message.length <= 30At most 10^4 calls will be made to shouldPrintMessage.
Examples
Example 1
shouldPrintMessage(1,"foo"); shouldPrintMessage(2,"bar"); shouldPrintMessage(3,"foo"); shouldPrintMessage(8,"bar"); shouldPrintMessage(10,"foo"); shouldPrintMessage(11,"foo")[true,true,false,false,false,true]Explanation: foo at t=1 (yes), bar at t=2 (yes), foo at t=3 (no, too soon), bar at t=8 (no, less than 10s), foo at t=10 (no, last was 1), foo at t=11 (yes, last allowed was 1 + 10 = 11).
Approaches
1. Hash map last-allowed-timestamp (optimal)
Store the next allowed timestamp per message. On a call, compare timestamp to the stored value.
- Time
- O(1) per call
- Space
- O(unique messages)
class Logger {
constructor() { this.nextAllowed = new Map(); }
shouldPrintMessage(timestamp, message) {
const next = this.nextAllowed.get(message);
if (next === undefined || timestamp >= next) {
this.nextAllowed.set(message, timestamp + 10);
return true;
}
return false;
}
}Tradeoff: O(1) per call, but memory grows with unique messages. Acceptable for typical workloads but unbounded for streams of unique messages.
2. Hash map with periodic cleanup
Same as above, but periodically sweep entries whose next-allowed time is in the past.
- Time
- Amortized O(1) per call
- Space
- Bounded by recent unique messages
class LoggerCleanup {
constructor() { this.nextAllowed = new Map(); this.lastSweep = 0; }
shouldPrintMessage(timestamp, message) {
if (timestamp - this.lastSweep > 100) {
for (const [k, v] of this.nextAllowed) {
if (v <= timestamp) this.nextAllowed.delete(k);
}
this.lastSweep = timestamp;
}
const next = this.nextAllowed.get(message);
if (next === undefined || timestamp >= next) {
this.nextAllowed.set(message, timestamp + 10);
return true;
}
return false;
}
}Tradeoff: Bounds memory growth for streams with many unique messages. Sweep cadence is a tradeoff between memory and amortized cost; tune to the workload.
3. Two-queue rolling buckets (variant)
Keep one queue per 10-second bucket. On call, pop expired buckets, then check if message is in current.
- Time
- Amortized O(1)
- Space
- O(active messages)
class LoggerBuckets {
constructor() { this.recent = new Set(); this.previous = new Set(); this.bucketStart = 0; }
shouldPrintMessage(timestamp, message) {
if (timestamp - this.bucketStart >= 10) {
this.previous = this.recent;
this.recent = new Set();
this.bucketStart = timestamp - (timestamp % 10);
}
if (this.recent.has(message) || this.previous.has(message)) return false;
this.recent.add(message);
return true;
}
}Tradeoff: Approximates a sliding window with two fixed buckets — same idea many production rate limiters use. Mention as the bridge to a real-world 'token bucket' or 'sliding-log' rate limiter follow-up.
Uber-specific tips
Uber interviewers grade this on the follow-ups, not the easy version. Expect them to ask: 'extend to per-user rate limit', 'extend to N requests per W seconds', 'distribute this across servers'. Have answers ready: per-user is keyed map, N-per-W is sliding-log or token bucket, distributed needs a shared store with monotonic timestamps.
Common mistakes
- Storing the timestamp of the LAST print instead of the next ALLOWED — both work but the boundary off-by-one differs.
- Forgetting that timestamps arrive in non-decreasing order — the problem guarantees it; don't add sorting.
- Not addressing unbounded memory growth when asked about long-running logger.
Follow-up questions
An interviewer at Uber may pivot to one of these next:
- Design a per-IP rate limiter, N requests per W seconds. (Sliding-log or token-bucket.)
- What if timestamps could be out of order? (Need a sorted structure or sliding window with reordering.)
- Distributed rate limiter — how would you share state across servers?
Solve it now
Free. No sign-up. Python and JavaScript run instantly in your browser.
FAQ
Why does this anchor a 'design a rate limiter' interview round?
LC 359 is the simplest rate-limit-style problem with a clean interface. Real interviews extend it to per-key sliding windows, token buckets, and distributed coordination — all built on the same hash-map foundation.
Is the cleanup variant necessary at the constraint scale?
Not for the 10^4 call bound — the basic map is fine. The cleanup version is what you mention when the interviewer asks 'what if this ran for years with unique messages?'.
Free learning resources
Curated free links for this problem.
Practice these live with InterviewChamp.AI
Drill Logger Rate Limiter and other Uber interview questions under real-loop conditions with instant feedback on your reasoning, complexity claims, and code.
Practice these live with InterviewChamp.AI →