Design Hit Counter
Overview
Design Hit Counter is a problem where you need to track the number of hits (requests, events, etc.) within a specific time window. A queue is perfect for this because it maintains timestamps in chronological order and automatically removes timestamps outside the window.
This problem demonstrates:
- Time-window tracking - Track hits within time window
- Automatic cleanup - Remove old timestamps outside window
- Efficient counting - Count hits efficiently
- Chronological order - Queue maintains timestamp order
Key characteristics:
- Time-based window - Track hits within time window (e.g., last 5 minutes)
- Queue of timestamps - Store hit timestamps in queue
- Automatic cleanup - Remove timestamps outside window
- O(1) operations - Efficient hit recording and counting
- Real-time - Handle hits as they arrive
How Hit Counter Works
Hit counter uses a queue to track timestamps:
- Record hit - Enqueue current timestamp
- Remove old - Remove timestamps outside time window
- Count hits - Return queue size (number of hits in window)
- Chronological order - Queue maintains timestamp order
The queue automatically maintains hits within the time window.
Basic Hit Counter Implementation
Simple hit counter using queue:
class HitCounter {
private queue: number[] = [];
private windowSize: number; // in seconds
constructor(windowSize: number = 300) { // Default 5 minutes
this.windowSize = windowSize;
}
hit(timestamp: number): void {
// Remove old hits outside window
this.cleanup(timestamp);
// Add new hit
this.queue.push(timestamp);
}
getHits(timestamp: number): number {
// Remove old hits outside window
this.cleanup(timestamp);
// Return count of hits in window
return this.queue.length;
}
private cleanup(timestamp: number): void {
// Remove timestamps outside window
while (this.queue.length > 0 && timestamp - this.queue[0] >= this.windowSize) {
this.queue.shift();
}
}
}Time: O(n) cleanup worst case, O(1) amortized, Space: O(n)
Limitation: shift() is O(n), making cleanup inefficient.
Pattern 1: LeetCode - Design Hit Counter
LeetCode problem implementation:
class HitCounter {
private queue: number[] = [];
hit(timestamp: number): void {
this.queue.push(timestamp);
}
getHits(timestamp: number): number {
// Remove hits older than 5 minutes (300 seconds)
while (this.queue.length > 0 && timestamp - this.queue[0] >= 300) {
this.queue.shift();
}
return this.queue.length;
}
}Time: O(n) worst case, O(1) amortized, Space: O(n)
Key insight: Remove timestamps older than 5 minutes.
Pattern 2: Optimized Hit Counter with Circular Buffer
Using circular buffer for better performance:
class HitCounterOptimized {
private timestamps: number[];
private hits: number[];
private windowSize: number;
private totalHits = 0;
constructor(windowSize: number = 300) {
this.windowSize = windowSize;
// Use array of size windowSize (one bucket per second)
this.timestamps = new Array(windowSize).fill(0);
this.hits = new Array(windowSize).fill(0);
}
hit(timestamp: number): void {
const index = timestamp % this.windowSize;
// If timestamp is for different second, reset bucket
if (this.timestamps[index] !== timestamp) {
this.timestamps[index] = timestamp;
this.totalHits -= this.hits[index];
this.hits[index] = 1;
this.totalHits++;
} else {
// Same second, increment
this.hits[index]++;
this.totalHits++;
}
}
getHits(timestamp: number): number {
let count = 0;
// Count hits in valid time window
for (let i = 0; i < this.windowSize; i++) {
if (timestamp - this.timestamps[i] < this.windowSize) {
count += this.hits[i];
}
}
return count;
}
}Time: O(1) hit, O(k) getHits where k is window size, Space: O(k)
Key optimization: Bucket hits by second, avoiding queue operations.
Pattern 3: Hit Counter with Deque
Using deque for efficient removal:
class HitCounterDeque {
private deque: number[] = [];
private windowSize: number;
constructor(windowSize: number = 300) {
this.windowSize = windowSize;
}
hit(timestamp: number): void {
this.deque.push(timestamp);
this.cleanup(timestamp);
}
getHits(timestamp: number): number {
this.cleanup(timestamp);
return this.deque.length;
}
private cleanup(timestamp: number): void {
// Remove from front (oldest timestamps)
while (this.deque.length > 0 && timestamp - this.deque[0] >= this.windowSize) {
this.deque.shift();
}
}
}Time: O(n) worst case cleanup, Space: O(n)
Note: Still uses shift(), but deque structure allows efficient front removal.
Pattern 4: Distributed Hit Counter
Hit counter that can handle distributed systems:
class DistributedHitCounter {
private queues: Map<string, number[]> = new Map();
private windowSize: number;
constructor(windowSize: number = 300) {
this.windowSize = windowSize;
}
hit(serverId: string, timestamp: number): void {
if (!this.queues.has(serverId)) {
this.queues.set(serverId, []);
}
const queue = this.queues.get(serverId)!;
queue.push(timestamp);
this.cleanup(queue, timestamp);
}
getHits(timestamp: number): number {
let total = 0;
for (const queue of this.queues.values()) {
this.cleanup(queue, timestamp);
total += queue.length;
}
return total;
}
private cleanup(queue: number[], timestamp: number): void {
while (queue.length > 0 && timestamp - queue[0] >= this.windowSize) {
queue.shift();
}
}
}Time: O(n × servers), Space: O(n × servers)
Use case: Track hits across multiple servers.
Pattern 5: Hit Counter with Rate Limiting
Hit counter that enforces rate limits:
class RateLimitedHitCounter {
private queue: number[] = [];
private windowSize: number;
private maxHits: number;
constructor(windowSize: number, maxHits: number) {
this.windowSize = windowSize;
this.maxHits = maxHits;
}
hit(timestamp: number): boolean {
// Cleanup old hits
this.cleanup(timestamp);
// Check rate limit
if (this.queue.length >= this.maxHits) {
return false; // Rate limit exceeded
}
// Record hit
this.queue.push(timestamp);
return true;
}
getHits(timestamp: number): number {
this.cleanup(timestamp);
return this.queue.length;
}
private cleanup(timestamp: number): void {
while (this.queue.length > 0 && timestamp - this.queue[0] >= this.windowSize) {
this.queue.shift();
}
}
}Time: O(n) cleanup, Space: O(n)
Key insight: Enforce rate limit by checking queue size.
Pattern 6: Hit Counter with Sliding Window
Hit counter with configurable window:
class SlidingWindowHitCounter {
private queue: number[] = [];
hit(timestamp: number): void {
this.queue.push(timestamp);
}
getHits(timestamp: number, windowSize: number): number {
// Remove hits outside window
while (this.queue.length > 0 && timestamp - this.queue[0] >= windowSize) {
this.queue.shift();
}
return this.queue.length;
}
// Get hits for multiple windows
getHitsMultipleWindows(timestamp: number, windows: number[]): Map<number, number> {
const result = new Map<number, number>();
for (const windowSize of windows) {
result.set(windowSize, this.getHits(timestamp, windowSize));
}
return result;
}
}Time: O(n × windows), Space: O(n)
Key insight: Support multiple window sizes.
Pattern 7: Hit Counter with Persistence
Hit counter that can persist state:
class PersistentHitCounter {
private queue: number[] = [];
private windowSize: number;
constructor(windowSize: number = 300) {
this.windowSize = windowSize;
// Load from storage if available
this.loadState();
}
hit(timestamp: number): void {
this.cleanup(timestamp);
this.queue.push(timestamp);
this.saveState();
}
getHits(timestamp: number): number {
this.cleanup(timestamp);
return this.queue.length;
}
private cleanup(timestamp: number): void {
while (this.queue.length > 0 && timestamp - this.queue[0] >= this.windowSize) {
this.queue.shift();
}
}
private saveState(): void {
// Save to storage (localStorage, database, etc.)
localStorage.setItem('hitCounter', JSON.stringify(this.queue));
}
private loadState(): void {
// Load from storage
const saved = localStorage.getItem('hitCounter');
if (saved) {
this.queue = JSON.parse(saved);
}
}
}Time: O(n) cleanup + storage, Space: O(n)
Key insight: Persist state for recovery.
When to Use Queue for Hit Counter
Use queue for hit counter when you see:
- "Hit counter" - Track hits/requests/events
- "Time window" - Track hits within time window
- "Rate limiting" - Enforce rate limits
- "Analytics" - Track user activity
- "Request counting" - Count API requests
- "Event tracking" - Track events over time
- "Sliding window" - Maintain sliding window of hits
Key distinction:
- Queue - Perfect for time-ordered timestamps
- Array - Can work but less semantic
- Circular buffer - More efficient for fixed window
Hit Counter Patterns
| Pattern | Description | Use Case |
|---------|-------------|----------|
| Basic | Simple queue of timestamps | General purpose |
| Optimized | Bucket-based with circular buffer | High performance |
| Rate Limited | Enforce maximum hits | Rate limiting |
| Distributed | Multiple servers | Distributed systems |
| Sliding Window | Configurable window sizes | Flexible analytics |
| Persistent | Save/load state | Recovery, persistence |
Remember:
- Queue for chronological timestamp tracking
- Cleanup old timestamps outside window
- Optimize with buckets for high performance
Template (Basic Hit Counter)
class HitCounterTemplate {
private queue: number[] = [];
private windowSize: number;
constructor(windowSize: number = 300) {
this.windowSize = windowSize;
}
hit(timestamp: number): void {
this.cleanup(timestamp);
this.queue.push(timestamp);
}
getHits(timestamp: number): number {
this.cleanup(timestamp);
return this.queue.length;
}
private cleanup(timestamp: number): void {
while (this.queue.length > 0 && timestamp - this.queue[0] >= this.windowSize) {
this.queue.shift();
}
}
}Template (Optimized with Buckets)
class HitCounterBucketsTemplate {
private timestamps: number[];
private hits: number[];
private windowSize: number;
private totalHits = 0;
constructor(windowSize: number) {
this.windowSize = windowSize;
this.timestamps = new Array(windowSize).fill(0);
this.hits = new Array(windowSize).fill(0);
}
hit(timestamp: number): void {
const index = timestamp % this.windowSize;
if (this.timestamps[index] !== timestamp) {
this.timestamps[index] = timestamp;
this.totalHits -= this.hits[index];
this.hits[index] = 1;
this.totalHits++;
} else {
this.hits[index]++;
this.totalHits++;
}
}
getHits(timestamp: number): number {
let count = 0;
for (let i = 0; i < this.windowSize; i++) {
if (timestamp - this.timestamps[i] < this.windowSize) {
count += this.hits[i];
}
}
return count;
}
}Time and Space Complexity
Basic Implementation
- hit: O(1) amortized, O(n) worst case
- getHits: O(1) amortized, O(n) worst case
- Space: O(n) where n is hits in window
Optimized with Buckets
- hit: O(1)
- getHits: O(k) where k is window size
- Space: O(k)
Note: Bucket-based approach provides better worst-case performance.
Practice Tips
- Cleanup on both operations - Cleanup in both hit() and getHits()
- Use buckets - For high performance, use bucket-based approach
- Handle edge cases - Empty queue, timestamps out of order
- Avoid shift() - Use optimized approach for better performance
- Window size - Consider window size in design
- Rate limiting - Can combine with rate limiting
- Distributed - Consider distributed systems requirements
Common Mistakes
- Not cleaning up - Forgetting to remove old timestamps
- Using shift() - O(n) operation, use buckets instead
- Wrong window calculation - Off-by-one errors in window size
- Not handling out-of-order - Timestamps arriving out of order
- Memory leaks - Not cleaning up old timestamps
- Race conditions - Not handling concurrent access
- Precision issues - Timestamp precision problems
Real-World Applications
Hit counter is used extensively in:
- Web Analytics - Track page views, user activity
- API Rate Limiting - Limit API requests per time window
- Monitoring Systems - Track system events, errors
- Social Media - Track likes, views, interactions
- E-commerce - Track product views, purchases
- Security - Track login attempts, suspicious activity
- Performance Monitoring - Track request rates
- Ad Serving - Track ad impressions, clicks
Related Concepts
- Queue - Data structure for timestamp tracking
- Sliding Window - General sliding window pattern
- Rate Limiting - Enforcing rate limits
- Time Series - Time-series data analysis
- Circular Buffer - Efficient window implementation
Summary
Design Hit Counter demonstrates:
- Time-window tracking - Track hits within time window
- Queue-based - Queue maintains timestamp order
- Automatic cleanup - Remove timestamps outside window
- Efficient counting - Count hits efficiently
- Real-time - Handle hits as they arrive
Key takeaways:
- Use queue to maintain chronological timestamps
- Cleanup old timestamps outside window
- Use bucket-based approach for high performance
- Essential for analytics, rate limiting, and monitoring
- Foundation for many time-window problems
Mastering hit counter design will help you solve time-window problems, implement rate limiting, and design efficient analytics systems in coding interviews.