System design interview guide
Design a Hit Counter: Last 5 Minutes, Ring Buffer and View Counts at Scale
Short answer
To design a hit counter for the last 5 minutes, keep 300 buckets, one per second, in a circular array. A hit at second t goes to bucket t mod 300, after resetting it if it holds an older second. getHits adds the buckets from the last 300 seconds. Memory stays at about 4.8 KB at any traffic. When hits per second are huge, each web server adds up its hits in memory. It sends one INCRBY per item per second to Redis. Very hot items are split into sub-keys, the window uses per-minute buckets, and a HyperLogLog counts unique visitors.
Asked to count ad clicks exactly, for billing? See the ad click aggregator design guide
Two courses by the author of this page:
770 lessons · 18 free to read
₹1,111 in India · $25 elsewhere, once
Get System DesignYou own this course
214 lessons · 10 free to read
₹1,333 in India · $35 elsewhere, once
Get Generative AI and Agentic AIYou own this course
Save your progress and get the interview checklist PDF
A hit counter looks like the smallest question in the whole interview list. Count the hits in the last five minutes. Then the interviewer asks one more thing. What if a million people hit it every second? Now it is a distributed systems question. One number is being changed by thousands of machines at once. That is the same problem behind a video's view count, a post's like count and a page's visitor count.
A hit counter records events and answers one question: how many happened recently. The classic version is LeetCode 362. It counts hits in the past 300 seconds. The best answer keeps 300 small buckets, one per second, and reuses them in a circle. So memory stays fixed at about 4.8 KB, however busy the site is. The follow-up turns it into a real system. Many web servers see the hits. A shared counter in one key gets hammered. So each server adds up its own hits for one second, then sends one total. Hot keys are split into sub-keys. The last-five-minutes view comes from time buckets. Unique viewers come from a HyperLogLog, a small sketch that estimates how many different people came.
Where it shows up
Asked as a warm-up or a full round at product companies everywhere. It appears as LeetCode 362, as "design a view counter", as "count YouTube views" and as "count clicks per page". It suits backend, platform and data roles. The same ideas come back in rate limiters, trending lists and ad click counting.
Spec sheeta hit counter
- 01Hits per second
- ~11,600 average, ~35,000 peak
- 02Memory for the classic counter
- 4,800 bytes
- 03All-time counters
- ~0.8 GB of raw numbers
- 04One viral item
- 10,000 hits a second on one key
worked through below, with the maths
Why this question is asked
It is small enough to finish and deep enough to separate levels. A junior answer stores every timestamp in a list. A good answer uses fixed buckets and explains why memory stops growing. A senior answer handles the follow-up. Many machines write one counter. Some counters are far hotter than others. The window must slide without scanning old data. And exact counts are not always worth their cost. Each step has a clear trade-off, so the interviewer can see how you think.
This page is the free part.
The course goes deeper on the hit counter design
₹1,111 in India$25 everywhere elseonce, for the whole course
The System Design course covers the hit counter design across a run of lessons, not one page. These 4 alone are about 80 minutes of step-by-step reading, every one with a quiz.
- Sliding Window
Rate limit with a moving time window, smoother than fixed windows and more accurate.
- Sliding Windows
Windows that slide with every event, continuous recalculation over the most recent N events or T time.
- Redis Cache
The same small GET measured 0.3 ms at worst on an idle server and 59.1 ms while one other client ran KEYS. Reading one field cost 0.934 ms as a blob and 0.040 ms as a hash.
- Micro-Batching
Process small batches at short intervals. Spark Structured Streaming approach to near-real-time.
all part of
System Design Masterclass
770 lessons · about 287 hours · 18 free to read
₹1,111in India, by UPI
$25everywhere else, by PayPal
It is really nice and good Please join the course
You own this course
Continue with Sliding Windowpay once,
yours for life
Live from the course bench
Before you design Hit Counter, try the questions it rests on.
Real interview questions, live diagrams and measured lab results, pulled at random from the lessons. A new set every time you come back.
Requirements
Always clarify these in the first 5 minutes of the interview. Do not start drawing boxes until both lists are agreed.
Functional requirements
- Record a hit for an item (a page, a video, a post) with its timestamp
- Return the number of hits for an item in the last 5 minutes
- Return the all-time hit count for an item
- Optionally, return the number of unique visitors for an item
- Show a count that updates within a few seconds
Non-functional requirements
- Memory per item must not grow with traffic
- Take about 35,000 hits a second at peak, by this guide's estimate
- A single viral item must not slow down the store for everyone else
- Reads must be fast, because every page view also shows the count
- A count may lag by a second or two, but must never go backwards or double count
Back-of-envelope scale estimates
Show your math. Pulling numbers from thin air signals you have not thought about the load.
Hits per second
~11,600 average, ~35,000 peak
Assume 1 billion views a day. 1,000,000,000 divided by 86,400 seconds is about 11,574 a second. Assume the busiest second is 3 times the average. That gives about 34,722 a second. These are interview assumptions, not any one company's numbers.
Memory for the classic counter
4,800 bytes
300 buckets, each holding a second and a count. Two numbers of 8 bytes each is 16 bytes. 300 times 16 is 4,800 bytes. It stays 4,800 bytes at one hit a second or a million.
All-time counters
~0.8 GB of raw numbers
Assume 100 million items have a counter. Each count is an 8-byte number. 100,000,000 times 8 is 0.8 GB. A key-value store adds some overhead per key on top, but the size stays small.
One viral item
10,000 hits a second on one key
Assume one video gets 10,000 views a second. Sent one by one, that is 10,000 writes a second to one key. One key lives on one shard, so that shard takes all of it.
Writes after batching
at most 200 a second per key
Assume 200 web servers. Each one adds up its hits in memory and sends one total a second. So one key gets at most 200 writes a second. That is 50 times fewer than 10,000. And the cap stays 200 even if traffic doubles.
Unique viewers
12 KB per item instead of 8 MB
Keeping the id of 1 million viewers takes about 8 MB for one item. A Redis HyperLogLog uses at most 12 KB and has a standard error of 0.81%. For 1 million viewers, that is usually within about 8,100.
High-level architecture
Start with one machine, because that is the question as asked. Keep an array of 300 buckets. Each bucket holds a second and the hits in that second. A hit at second t goes to bucket t mod 300. If the bucket still holds an older second, reset it first. To read, add up the buckets whose second is inside the last 300. Then scale it out. Hits arrive at many web servers behind a load balancer. Each server keeps a small map of item to count in memory. Once a second it sends the totals to a counter store, such as Redis, with one INCRBY per item. The store keeps three things per item. There is an all-time total. There are time buckets for the recent window. And there is an optional HyperLogLog for unique visitors. Very hot items are split into several sub-keys, and reads add them up. A copy of every hit can also go to a log, such as Kafka. A stream job can rebuild counts from it if the store is lost, and analytics can read it later. Reads come from the counter store, through a short cache for the hottest items.
In a real interview, sketch this on the whiteboard before diving into any single box.
One view, end to end
A person opens a video page. Here is what happens to that one hit.
- 1
The hit arrives
The web server serves the page and adds 1 to the item's count in its own memory. No network call is made yet.
- 2
The server flushes
Once a second, the server sends one INCRBY per item it saw, with that second's total. A busy item gets one write from this server, not thousands.
- 3
The store updates
The counter store adds the total to the all-time count and to the bucket for the current minute. For a hot item, it picks one of the item's sub-keys at random.
- 4
The log keeps a copy
The same totals, or the raw hits, are written to a log. If the store is ever lost, the counts can be rebuilt from it.
- 5
Someone reads the count
The page asks for the count. The store adds the sub-keys and the last five minute buckets. A cache keeps the answer for a second or two for very hot items.
Core components
Walk through each service. The interviewer wants to hear what each one owns, not just the names.
Ring buffer (one machine)
An array of 300 buckets, one per second. A hit at second t goes to bucket t mod 300. Old seconds are overwritten, so memory never grows. This is the LeetCode 362 answer.
Local aggregation in each web server
A small in-memory map of item to count. It is flushed once a second. This turns thousands of tiny writes into one write per item per server. The cost is a second of delay, and a crash can lose up to one second of that server's hits.
Counter store
A fast key-value store with atomic increments, such as Redis. INCRBY adds to a number in one step, so two servers adding at once never lose an update. It holds all-time totals and recent time buckets.
Sharded hot keys
A very hot item gets N sub-keys, like video:42:0 to video:42:15. Each write goes to one at random. A read adds all N. The writes spread over many shards instead of one.
Time buckets for the window
Per-minute buckets with a short time to live. The last five minutes is the sum of five buckets plus the current one. Old buckets expire on their own, so nothing has to delete them.
HyperLogLog for unique visitors
A small sketch that estimates how many different visitors came. In Redis, PFADD adds a visitor and PFCOUNT returns the estimate. It uses at most 12 KB per item, whatever the number of visitors.
Event log and stream job
A durable log of hits or per-second totals, such as Kafka. A stream job can rebuild counts, fix them after an outage, and feed analytics. It is the source of truth when exact counts matter.
Data model
Pick the right store per table. Justify each choice with the access pattern, not by reflex.
ring buffer (in memory)times[300]counts[300]The one-machine answer. Bucket i holds the second it was last used for and the hits in that second. 4,800 bytes in total.
count:<item> (all-time)item_idtotal (64-bit integer)One number per item, changed only by INCRBY. Hot items use count:<item>:<0..N-1> and reads add them up.
win:<item>:<minute> (window buckets)item_idminute (unix seconds / 60)hitsttl ~ 10 minutesRecent hits per minute. A read adds the last five full minutes and the current one. The time to live cleans them up.
uv:<item> (unique visitors)item_idHyperLogLog sketch (up to 12 KB)Estimated unique visitors. Per-day sketches can be merged with PFMERGE to get a week.
hit_events (log)item_idsecondcountserver_idbatch_idPer-second totals from each server, kept for a few days. The batch_id lets a consumer skip a batch it has already applied.
Deep dives
These are the conversations the interviewer is steering you toward. Practice each one until you can talk through it without notes.
The one-machine answer: a ring of 300 buckets
The simple answer keeps every timestamp in a queue. To read, drop timestamps older than 300 seconds and count the rest. That works, but memory grows with traffic. At 5,000 hits a second, the queue holds 1.5 million entries. The better answer keeps 300 buckets, one per second. A hit at second t goes to bucket t mod 300. If that bucket still holds an older second, reset its count to zero first. To read at time t, add the buckets whose second is greater than t minus 300. Memory is 300 pairs of numbers, 4,800 bytes, at any traffic. I ran it on the LeetCode example: hits at seconds 1, 2, 3 and 300. Reads at 4, 300 and 301 return 3, 4 and 3, as expected. A read after a quiet gap returns 0, because every bucket holds an old second.
def hit(self, ts):
i = ts % 300
if self.times[i] != ts: # bucket holds an old second
self.times[i] = ts
self.counts[i] = 0
self.counts[i] += 1
def get_hits(self, ts):
return sum(c for t, c in zip(self.times, self.counts)
if ts - t < 300)
Making it safe with many threads
On one machine, many threads may call hit at once. Two threads can both read a bucket, add one, and write back. One hit is lost. There are three common fixes. A lock around hit and get_hits is simple and correct, but threads wait for each other. Atomic counters per bucket avoid the lock for the add, but the reset of an old bucket still needs care. A third choice is one counter per thread, added up on read. That is the same idea as the distributed design: write locally, add up later. In an interview, say which one you pick and why. For a warm-up round, a lock is fine.
Batching: one write per server per second
Now there are 200 web servers. The obvious design sends one INCR to Redis for every hit. At about 35,000 hits a second, that is 35,000 network calls a second. Worse, one viral video can take 10,000 of them, all on one key. Instead, each server keeps a map of item to count in memory. Once a second it sends one INCRBY per item with the total. A key now gets at most one write per server per second. With 200 servers, that is 200 writes a second, 50 times fewer than 10,000. And the number does not grow with traffic. The cost is a delay of up to one second. A server crash also loses up to one second of its own hits. For a view count, both are fine. For billing, they are not, and the ad click aggregator guide covers that case.

Hot keys: split one counter into many
Batching caps the writes per key, but a store with many shards still sends one key to one shard. If a few keys get most of the traffic, those shards are busy and the rest are idle. The fix is to split a hot counter into N sub-keys, such as video:42:0 to video:42:15. Each write picks one at random. I ran this with 10,000 writes. With 16 sub-keys, the busiest got 670 and the quietest 583. The sum was still exactly 10,000. A read now adds 16 values instead of one. So only split the keys that need it. A simple rule: split an item once its write rate passes a threshold. Keep the list of split items in config.
The sliding window at scale
The ring buffer keeps 300 per-second buckets. At scale, that is 300 keys per item, and a read adds 300 numbers. Most products do not need one-second steps. So keep per-minute buckets instead. The last five minutes is the sum of the five full minutes before now, plus the current minute. That is six small reads. Each bucket has a time to live of about ten minutes, so old buckets disappear on their own. The trade-off is precision. The window moves in one-minute steps. So a count can include up to a minute of extra hits at the edge. If the interviewer needs exact seconds, keep per-second buckets for the recent minute only. That is a hybrid: fine buckets near now, coarse ones further back.
Exact or approximate
Total hits are easy to keep exactly, because a counter is one number. Unique visitors are harder. An exact answer needs the set of everyone who came. For 1 million visitors, that is about 8 MB for one item. A HyperLogLog estimates the same number in at most 12 KB. Redis documents a standard error of 0.81%. For 1 million visitors, the answer is usually within about 8,100. Most products show "1.2M views", so that error is invisible. Use exact counting when money depends on it, such as paying creators or billing ads. Then the log and a batch job give the exact number, and the fast counter is only for display.
Never losing or doubling counts
Counts must not go backwards, and a retry must not add a batch twice. A server that times out while sending a batch does not know if it landed. If it simply sends again, the count may grow twice. So each batch carries an id made of the server and the second. The consumer keeps recent batch ids for a few minutes and skips any it has seen. The log is the backstop. If the counter store loses data, a stream job replays the log and rebuilds the totals. That is why the log keeps per-second totals, not only the latest number.
Trade-offs to discuss
Every senior interviewer expects you to surface at least 3 of these. Pick the decisions, state the alternatives, and justify your choice.
Ring buffer versus a queue of timestamps
A queue is exact to the second and easy to explain. But it grows with traffic and can hold millions of entries. The ring buffer is fixed at 300 buckets. It loses nothing, because hits within one second do not need separate timestamps.
Write every hit versus batch per server
Writing every hit gives the freshest count. But it sends one network call per hit and hammers hot keys. Batching once a second cuts writes to one per item per server. The cost is a second of delay and up to a second of hits lost if a server crashes.
One key versus sub-keys for hot items
One key is simple, and reads are one lookup. Sub-keys spread a hot item over many shards. But reads must add N values. So split only the items that are hot.
Per-second versus per-minute buckets
Per-second buckets give an exact five-minute window, at 300 values per item. Per-minute buckets need about six values, but the window moves in one-minute steps. Pick by what the product shows.
Exact unique visitors versus HyperLogLog
An exact set gives the true number but costs memory per visitor. A HyperLogLog costs at most 12 KB with about 0.81% error. Use the sketch for display and an offline exact count when money depends on it.
Free PDF · 18 pages
Get the free System Design Interview Cheat Sheet
The interview in seven stages, the numbers worth knowing by heart, and twelve classic systems on one page each, every line linked to the lesson it comes from.
Follow-up questions to expect
After the main design, the interviewer usually picks one part and asks more. Here is what to say.
What if the hits do not arrive in time order?
LeetCode 362 promises time order, but real servers have clock drift and delays. With buckets, a late hit is fine if its second is still inside the window. It goes to its own bucket. Hits older than the window are dropped or sent to the log only. Use the server's receive time, not the client's clock, so a wrong phone clock cannot add hits to the future.
How would you show the top 10 most viewed items right now?
Keep a sorted set of item to recent count, updated from the same per-second batches. Read the top 10 from it. For very many items, keep a count-min sketch and a small heap per server, then merge. The top-k guide goes deeper into this.
How do you stop bots from inflating views?
Count raw hits fast for display, but decide the official number later. A stream job can drop repeated hits from one visitor in a short time. It can also drop hits from known bot networks and hits with no real playback. The public count can then be corrected, which is why some sites show a lower number after review.
How a hit counter actually does it
The one-machine problem is LeetCode 362, Design Hit Counter. It counts hits in the past 300 seconds. Timestamps are in seconds and arrive in order. Its follow-up asks what happens when hits per second are huge. The distributed design here uses well-documented building blocks. Redis INCR is atomic, and its docs list the counter as its first use. A Redis HyperLogLog uses at most 12 KB with a standard error of 0.81%. The traffic numbers on this page are interview assumptions. They are not the numbers of any real company.
Lessons to study before this interview
If any of these topics are fuzzy, the interviewer will catch it. Each lesson is 15 to 60 minutes with diagrams, code, and a quiz.
Sliding Window
intermediate / api design protocols
Sliding Windows
advanced / stream batch processing
Redis Cache
foundation / caching strategies
Micro-Batching
advanced / stream batch processing
Database Sharding
foundation / database fundamentals
Stream Processing
advanced / stream batch processing
Counter Metrics
intermediate / observability monitoring
Idempotent Consumer
intermediate / messaging event systems
Frequently asked questions
Go deeper
This guide is built on these 6 lessons. Each one teaches one piece of the hit counter design above, step by step.
- Sliding WindowSystem Design · api design protocols · 15 min
- Sliding WindowsSystem Design · stream batch processing · 19 min
- Redis CacheSystem Design · caching strategies · 26 min
- Micro-BatchingSystem Design · stream batch processing · 20 min
- Database ShardingSystem Design · database fundamentals · 35 min
- Stream ProcessingSystem Design · stream batch processing · 30 min
Practice with 770 system design lessons
Lifetime access for ₹1,111 in India or $25 elsewhere. Interactive diagrams, quizzes, and 20 capstone projects to practise on.
This one question is not enough for the interview.
Get all 200 questions in the System Design and Distributed Systems pack
Each question has why it is asked, a full answer, what the interviewer listens for, the follow-ups, the common mistakes and a diagram (268 diagrams in all). A PDF you keep, junior to staff level.
India: UPI, cards or netbanking through Razorpay. Elsewhere: card or PayPal. Download at once from My packs.
NEWSystem Design Hands-On
Included in the System Design course: real systems, built live
Postgres Sharding: Complete Hands-On (PgBouncer + Citus). 2 h 36 min and 20 chapters: one PostgreSQL loaded until it breaks, then four shards behind a router you write, PgBouncer and Citus, with the 139-page slide deck. The first 15 minutes are free to watch.

