Presence States
- online — active heartbeat within the last 30 seconds
- away — active within the last 5 minutes but not in the last 30 seconds
- offline — no activity for more than 5 minutes
Heartbeat Mechanism
Client sends a ping every 15 seconds. Server updates presence in Redis:
SETEX user:{id}:heartbeat 30 {timestamp}
The 30-second TTL ensures the key auto-expires if the client disconnects without an explicit logout.
Status Derivation
GET user:{id}:heartbeat
-> key exists, age < 30s => online
-> key exists, age < 5min => away
-> key missing => offline
Presence Events and Fan-Out
On state change, publish to Redis channel:
PUBLISH presence:{user_id} {state}
A subscriber service maintains the friendship graph. On each presence event it fans out notifications to all friends with active WebSocket connections.
High-Follower Fan-Out
For users with more than 10K followers, synchronous fan-out is too slow. Instead, push the event to an async fan-out queue (e.g., Kafka/SQS). Workers consume and deliver to followers in batches.
Last-Seen Storage
UserPresence (
user_id BIGINT PRIMARY KEY,
last_seen_at TIMESTAMP
)
Written to DB when a user transitions to offline, so last-seen can be displayed even for users who have been offline for days.
Privacy
Each user has a per-account setting to hide their online status. If enabled, presence events are suppressed and API responses return null for that user.
Presence Subscription
On app load, the client sends a list of contact user IDs. The server subscribes the client to those presence channels and streams state changes in real time.
Batch Presence API
For rendering a contact list efficiently:
GET /presence/batch?user_ids=1,2,3,4,5
Response:
{
"1": "online",
"2": "away",
"3": "offline"
}
Server performs a Redis pipeline MGET for all requested keys, derives states, and returns in a single response.
Architecture Summary
Client --heartbeat ping--> API Server --SETEX--> Redis
|
PUBLISH presence:{uid}
|
Subscriber Service
|
Fan-out to friends via WS / queue
{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “How does a presence service determine if a user is online?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “The client sends a heartbeat ping every 15 seconds. The server stores a Redis key with a 30-second TTL. If the key exists and its age is under 30 seconds, the user is online. If the age is under 5 minutes, the user is away. If the key is missing, the user is offline.”
}
},
{
“@type”: “Question”,
“name”: “How do you scale presence fan-out to users with many followers?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “For users with more than 10K followers, synchronous fan-out is too slow. Instead, presence change events are pushed to an async fan-out queue such as Kafka or SQS. Workers consume events in batches and deliver notifications to followers.”
}
},
{
“@type”: “Question”,
“name”: “How is last-seen stored for users who have been offline for a long time?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “When a user transitions to offline, last_seen_at is written to a UserPresence table in the database. This allows the application to display accurate last-seen timestamps even after the Redis key has expired.”
}
},
{
“@type”: “Question”,
“name”: “How do you fetch presence status for an entire contact list efficiently?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “A batch API endpoint GET /presence/batch?user_ids=1,2,3 uses a Redis pipeline MGET to fetch all heartbeat keys in a single round trip, derives each user's state, and returns the results in one response.”
}
}
]
}
See also: Meta Interview Guide 2026: Facebook, Instagram, WhatsApp Engineering
See also: Snap Interview Guide
See also: Atlassian Interview Guide