Designing a Live-Class Attendance System at Scale
A system design problem involving heartbeats, reconnects, Kafka, and bitmap-based attendance.

Recently, I was asked a system design question in an interview that sounded straightforward at first:
How would you design an attendance system for a live class where a student must attend at least 150 minutes of a 180-minute session, even if they disconnect and reconnect multiple times?
The interesting part wasn't calculating 150 minutes. It was figuring out how to calculate it reliably when millions of students are connected, devices lose connectivity, events arrive out of order, and storing every heartbeat becomes expensive.
I started with a simple architecture on the whiteboard and gradually explored how I would make it scalable and fault-tolerant.
This is my attempt to document that design, the trade-offs I considered, and the questions I think are worth discussing further.
1. Understanding the problem
Let's establish the requirements before choosing any technology.
- A live class lasts 180 minutes.
- A student needs at least 150 minutes of valid attendance to qualify as present.
- Students can disconnect and reconnect multiple times.
- Brief network interruptions should be tolerated according to a defined policy.
- A device can shut down without sending a disconnect event.
- A student may switch browsers or devices.
- Events may be duplicated, delayed, or processed out of order.
- The system should support millions of concurrent students.
The first important question is what counts as attendance.
Consider this timeline:
| Time | Activity | Attendance credited |
|---|---|---|
| 10:00–10:50 | Connected and active | 50 minutes |
| 10:50–10:52 | Disconnected | 0 minutes |
| 10:52–11:42 | Reconnected and active | 50 minutes |
| 11:42–11:44 | Disconnected | 0 minutes |
| 11:44–12:34 | Reconnected and active | 50 minutes |
| Total | 150 minutes |
Under a strict policy where disconnected time does not count, this student qualifies as present. A different product might choose to count very short interruptions toward attendance. That is also possible, but it should be an explicit business rule rather than an accidental consequence of the implementation.
For the rest of this design, I'll assume disconnected time does not count unless a separately defined grace policy says otherwise.
2. Starting with the basic calculation
The simplest solution is to record every valid attendance interval. For each student and class, we maintain intervals such as:
Then calculate:
Total attendance = sum of valid interval durations
The student qualifies when:
Total attendance ≥ 150 × 60 = 9,000 seconds
This immediately rules out one tempting but incorrect approach:
That calculation includes disconnected periods and can significantly overestimate attendance. We need to track multiple valid intervals under a single logical attendance record.
3. First-pass architecture
I started by separating connection handling, event ingestion, session processing, and persistence.

First Pass Architecture
The responsibilities are straightforward:
- WebSocket gateway: Manages persistent client connections.
- Attendance API: Validates events and student access to the class.
- Session processor: Tracks active intervals and handles reconnects.
- Redis: Stores frequently accessed session state.
- PostgreSQL: Stores durable attendance intervals, summaries, and final status.
- Attendance calculator: Calculates accumulated valid duration.
This is a logical architecture, not a claim that every system needs these exact components. For example, Redis might not be necessary if the processing layer already provides suitable state management.
The next question was what happens when the number of connected students becomes very large.
4. Heartbeats at millions of concurrent connections
Suppose each connected student sends a heartbeat every 30 seconds. With three million concurrent students, the average heartbeat rate becomes:
That's 100,000 heartbeat events per second, excluding joins, reconnects, and other events. Sending a separate HTTP request for every heartbeat would introduce significant request-processing overhead.
My first improvement would be to reuse an existing WebSocket connection if the live-class player already has one. This avoids repeatedly establishing HTTP connections, although it does not eliminate the heartbeat messages themselves.
How heartbeat tracking works
When a student joins:
- Authenticate the student and validate class access.
- Create or resume the logical attendance record.
- Create a connection-session identifier.
- Record the server-side join time and last-seen timestamp.
When a heartbeat arrives:
- Validate the session and event identity.
- Record the server-side receive timestamp.
- Update
last_seen_at. - Update or schedule the relevant attendance time slot.
When heartbeats stop arriving, the backend waits for an inactivity timeout before considering the session disconnected.
For example, if heartbeats arrive every 30 seconds, we could initially configure a 90-second inactivity timeout. This is a configurable policy, not a universal default.
One important limitation: if the device loses power between heartbeats, we cannot know its exact disconnect time from heartbeat events alone. The system must define how much attendance credit, if any, is allowed for that uncertain interval.
5. Handling reconnects and browser changes
A browser session should not define the student's identity for attendance purposes. I'd maintain a logical record keyed by:
Each individual connection can have a separate session_id. This lets the student reconnect from a new browser or device without creating a new attendance record.

Handling reconnects and browser changes
A reconnect opens another valid interval under the same logical attendance record.
We also need to prevent overlapping sessions from double-counting attendance. If a student has two devices connected simultaneously, the system should either enforce a single active session or calculate the union of valid intervals.
6. Introducing Kafka for event processing
At this point, I would consider Kafka to decouple event ingestion from attendance processing.

Kafka introduction
The ingestion layer can accept events and publish them to Kafka. Consumers can process events asynchronously, while buffering and replay capabilities help with temporary downstream failures.
A possible event format is:
The server should add its own receive timestamp. Client timestamps can help diagnose delays, but they should not be blindly trusted for attendance calculations.
Partitioning
A reasonable starting point is a Kafka key derived from:
That keeps events for the same student and class on the same partition. But there's an important distinction: Kafka preserves the order in which records are appended to a partition. It does not automatically sort events chronologically by their timestamps. This becomes especially important when multiple producers, retries, or asynchronous processing introduce reordering.
7. What if LEAVE arrives before JOIN?
Suppose a student joins at 10:00:00 and leaves at 10:00:20. Due to delays or producer-side concurrency, the consumer receives the events in this order:
If the processor discards LEAVE because it hasn't seen JOIN, it may lose attendance information. Instead, I'd use the following strategy:
- Give every event a unique
event_idfor deduplication. - Use per-session sequence numbers when the producer can generate them reliably.
- Retain unmatched events temporarily.
- Buffer events within a bounded reordering window.
- Reconcile late events against durable session state.
- Recompute affected intervals when necessary.
A bounded window helps avoid waiting indefinitely, but it cannot guarantee that all events arrive within the configured period. The system must also support late-event corrections. State updates should be idempotent so replaying an event does not count attendance twice.
8. Optimizing storage with bitmaps
Storing every heartbeat permanently is unnecessary when the main requirement is to calculate attendance duration. An alternative is to divide the class into fixed-duration slots and represent qualifying attendance slots using a bitmap. Suppose each slot represents 30 seconds.
For a 180-minute class:
Each slot corresponds to one bit:
1means the slot qualifies as attended.0means the slot is not confirmed as attended.
An illustrative bitmap might look like this:
The zeroes represent slots that did not qualify as attended, such as a disconnection period.
Calculating attendance with popcount
For a 180-minute class with 30-second slots, there are 360 bits. To qualify for 150 minutes, the student needs:
Therefore:
popcount counts the number of bits set to 1.
A 360-bit bitmap requires just 45 bytes of raw data per student per class. For three million students, that is approximately 135 MB of raw bitmap data for one class, excluding metadata, indexing, replication, and storage overhead.
But when do we set a bit?
This is the critical design decision. A heartbeat received at 10:00:30 does not automatically prove that the student attended the entire preceding 30-second interval. The heartbeat might be delayed, duplicated, or received after the student has stopped watching. We need a defined slot-assignment policy. For example, a slot can remain provisional until enough valid activity evidence exists, after which it can be marked as attended or not attended.
The system should also distinguish confirmed slots from unresolved ones. Otherwise, treating every missing heartbeat as an immediate zero can penalize temporary network issues, while treating every expected heartbeat as a one can overcount attendance.
A bitmap is an efficient representation of attendance, but the policy and event-processing logic determine its correctness.
9. Redis and PostgreSQL: what belongs where?
I'd separate fast-changing session state from durable attendance records.
Redis
Potential uses include:
- Active session metadata.
- Last-seen timestamps.
- Current accumulated attendance.
- Recent bitmap updates.
- Short-lived pending events.
Redis reduces the need to read and write the durable attendance record for every heartbeat. However, Redis should not be the only source of truth unless its durability and recovery guarantees are sufficient for the product's requirements.
PostgreSQL
I'd use PostgreSQL for durable records such as:
- Logical student-class attendance.
- Session and interval boundaries.
- Durable attendance summaries.
- Final attendance status.
- Recovery metadata where required.
Rather than permanently inserting every heartbeat, the system can persist meaningful interval transitions, periodic checkpoints, and final summaries. The system must still ensure that a crash or consumer replay cannot double-count an interval or lose already-confirmed attendance.
10. Failure scenarios
A few cases I would explicitly design for:
| Scenario | Approach |
|---|---|
| Battery dies | Detect stale activity using server-side timeout |
| Browser changes | Resume the same logical student-class attendance record |
| Duplicate heartbeat | Deduplicate and apply updates idempotently |
LEAVE arrives before JOIN | Buffer or reconcile the event |
| Consumer crashes | Recover state and replay events safely |
| Redis state is lost | Rebuild or reconcile from durable records and replayable events |
| Student reconnects after a long gap | Start a new interval according to policy |
| Multiple devices overlap | Enforce a session policy or merge intervals without double-counting |
| Consumer lag increases | Monitor lag, apply backpressure, and scale processing |
| Class ends with open sessions | Cap intervals at class end and reconcile pending events |
There is also a distinction between connection liveness and actual playback. A connected socket does not necessarily mean the video is playing, so the system should use appropriate activity signals if the attendance policy requires them.
11. What I would measure before calling this production-ready
Three million concurrent connections is a capacity target, not proof that the design will work at that scale.
I'd validate:
- WebSocket gateway memory, connection limits, and network capacity.
- Average and peak event ingestion rates.
- Kafka partition throughput and consumer lag.
- Reconnect storms following gateway or network failures.
- Redis memory, throughput, and recovery behavior.
- Durable storage write amplification.
- Attendance accuracy under delayed, duplicated, and out-of-order events.
- Recovery time following consumer restarts and partial infrastructure failures.
At 30-second heartbeat intervals, the average rate is 100,000 events per second for three million connected students. Real traffic will be uneven, particularly when popular classes start together or large numbers of clients reconnect after an outage. I'd test these conditions rather than relying only on average throughput estimates.
Final thoughts
The interesting part of this problem is not simply counting minutes. It is reconstructing valid attendance intervals from imperfect, delayed, and sometimes missing signals.
Heartbeats provide activity evidence. Timeouts help detect disconnected sessions. Kafka separates ingestion from processing, while event-time handling and reconciliation protect against out-of-order events. Redis can hold fast-changing state, PostgreSQL can preserve durable records, and bitmaps can represent attendance compactly.
There are several reasonable ways to implement this system. The best choice depends on the required attendance accuracy, acceptable delay in finalizing results, operational complexity, and scale.
If I were taking this design further, I'd want to explore three questions:
- Would you use a bitmap, interval-based records, or a hybrid approach?
- How would you handle short disconnects without unfairly crediting time when the client is offline?
- At three million concurrent students, would you choose Kafka-based processing, stateful stream processing, or a simpler architecture?
I'd love to hear how other engineers would approach this problem, especially what they would change in the architecture and why.