SDSystem design
Start learning free

LESSON 81 OF 140 · PRACTICE PROBLEMS

Online Presence Indicator

Read the written walkthrough

SYSTEM DESIGN · INTERVIEW WALKTHROUGH

Design an Online Presence Indicator: System Design Interview Practice

The problem

"Design an online presence indicator": the green dot next to a friend who has the app open, and "last seen 17:40" for one who has left. The dot is easy; the interviewer wants to hear about its cost: every open app keeps saying "I'm still here", and every change must reach the people looking at it, without flickering when a phone's signal comes and goes.

This practice problem reuses the pattern of Design Chat (WhatsApp), where presence was one deep dive. Here it is the whole product: the decisions are where each beat goes, when "last seen" is written, who hears about a change, and how long to wait before showing someone offline. Park custom statuses ("In a meeting") and privacy settings such as hiding your last seen.

The numbers

From the interviewer: at busy hours 100 million apps are open at once, and each one sends a heartbeat every 30 seconds, as in Chat.

what              worked out                       about
heartbeats        100 million ÷ 30 s               3.3 million a second
in memory         3.3 million ÷ 100,000 a server   33 servers
in a database     3.3 million ÷ 5,000 a server     670 servers
last-seen writes  3.3 million ÷ 30 beats a visit   110,000 a second

That is more than three times the messages Chat carries at busy hours (1 million a second), and almost no beat carries news.

Where each heartbeat goes

Writing last_seen = now to the users database on every beat is the first idea, and the numbers sink it: at about 5,000 writes a second per server, that's about 670 database servers writing to disk a time that is out of date 30 seconds later. One shared list of everyone online is no better: every beat hits the same key, and one key lives on one server.

Instead, give each user a key with a TTL in an in-memory store, split by user id. Each beat renews Ben's key; at about 100,000 writes a second per server, that takes about 33 servers. When the beats stop, the key expires on its own: if the server holding Ben's WebSocket crashes, nobody is left to say he went offline, but his key still runs out. A missing key means offline.

Last seen, written once

An expired key takes "online" with it, but Ben's friends should still see "last seen 18:04" tomorrow. So last seen goes to the users database, and only when it changes: once, when Ben goes offline. The store announces each key that expires, and an app that is closed properly says goodbye, which writes it at once. The time written is his last beat's, one TTL before the key ran out.

Say a visit lasts about 15 minutes: 30 beats, one change. Writing on the change is 30 times fewer writes, about 110,000 a second instead of 3.3 million. Two shortcuts fail: writing on every beat "as a backup" brings the 3.3 million back, and the time of his last message is wrong for anyone who opens the app, reads and leaves.

Who hears about a change

Say Ben has 200 friends. Telling them all that he left would mostly reach phones in pockets. Only friends with Ben on screen need it: Ana, whose chat list shows him, and Cy, whose chat with him says "online" in the header.

So presence is subscribed to. When Ana's chat list opens, her server subscribes to the presence channels of the people on it, a channel per user as in Real-Time Updates, and unsubscribes when the list closes. Ben's change is published once on his channel and reaches only the servers with subscribers, which push it down Ana's and Cy's WebSockets. Everyone else reads the dots once, when a screen showing Ben opens. Polling instead, every app asking about its 200 friends every 30 seconds, would be about 670 million lookups a second, nearly all "no change".

No flicker on a bad network

Phone networks deliver beats late, or not at all. Ben is on a train: his app beats every 30 seconds, each beat lands a second or two late, and the one sent at 60 seconds is lost in a tunnel.

sent    lands   key with a 30 s TTL          key with a 75 s TTL
0 s     1 s     online until 31 s            online until 76 s
30 s    32 s    offline at 31 s, back at 32  online until 107 s
60 s    lost    offline at 62 s              still online
90 s    92 s    online again at 92 s         online until 167 s
120 s   121 s   online until 151 s           online until 196 s

A TTL equal to the beat flickers: 4 changes in 2 minutes, each pushed to everyone watching and each offline a last-seen write. With about 75 seconds it takes 2 missed beats in a row to show Ben offline: one lost beat is covered, with slack for late ones, and a phone that really died still goes grey 75 seconds after its last beat. Ten minutes would never flicker, but a dead phone would look online for 10 minutes.

Same pattern as Chat (WhatsApp)

The connection carries over unchanged: each open app keeps one WebSocket to a server, which pushes a change the moment it happens, and a phone that loses signal is noticed only by its silence. In a full chat app, the presence servers would be Chat's connection servers doing one more small job per beat.

Summary

  • Scope: online or last seen next to every friend, live.
  • Data: a key per open app, with a TTL, in memory; last seen in the users database.
  • Numbers: about 3.3 million beats a second at busy hours.
  • Scale: about 33 in-memory servers, split by user id.
  • Last seen: written once, when someone goes offline.
  • Who hears: only friends with you on screen, through your channel.
  • Flicker: a TTL of about 75 seconds, so it takes 2 missed beats.
Keep going with system designInteractive lessons from the building blocks to Twitter, Uber and payments, the way interviews ask. Free to start.
Start learning free