Ephemeral State Design
Typing state is intentionally ephemeral — stored only in Redis, never persisted to the database. There is no value in querying who was typing yesterday.
Redis Key Structure
Key: typing:{conversation_id}:{user_id}
Value: 1
TTL: 5 seconds
Redis keyspace notifications are enabled. When a key expires, a stopped-typing event is triggered, allowing the system to broadcast that the user stopped typing without any explicit client action.
Typing Start Flow
Client sends typing_start event
-> Server: SETEX typing:{conv_id}:{user_id} 5 1
-> Server publishes typing event to Redis pub/sub channel chat:{conv_id}
-> WebSocket nodes push typing event to all connected participants (excluding sender)
Typing Stop Flow
Client sends typing_stop event OR Redis TTL expires
-> Server publishes stopped event to chat:{conv_id}
-> WebSocket nodes push stopped event to participants
Debounce Strategy
- Client sends
typing_startevery 3 seconds while the user is actively typing. - Server-side TTL is 5 seconds — longer than the client interval.
- If the client disconnects abruptly, the key expires in at most 5 seconds, cleaning up state automatically.
Broadcast via Redis Pub/Sub
PUBLISH chat:{conversation_id} {"type":"typing","user_id":123}
All WebSocket nodes subscribed to that channel receive the event and forward it to clients in that conversation. The sender is excluded from delivery.
Participants List
Participants for a given conversation are read from the ConversationMember table (cached in Redis). Only members receive typing events.
Group Chat Display
To show "Alice and Bob are typing…", the client aggregates active typers received from the server. The server can also provide a batch query:
KEYS typing:{conv_id}:* -- scan active typers in a conversation
-> returns list of user_ids currently typing
Rate Limiting
Maximum 1 typing_start event per user per 2 seconds is enforced server-side to prevent event spam from misbehaving clients.
State Recovery on Reconnect
When a WebSocket client reconnects, it queries active typers in the current conversation:
SCAN typing:{conv_id}:*
-> returns all user_ids with active typing keys
This restores the typing indicator UI without waiting for new events.
Architecture Summary
Client --typing_start--> WS Node --SETEX + PUBLISH--> Redis
|
Other WS Nodes (subscribed)
|
Push to participants
{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “Why is typing indicator state stored in Redis instead of a database?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Typing state is ephemeral and only meaningful in the moment. Redis provides sub-millisecond reads and writes, supports TTL-based auto-expiry, and keyspace notifications for cleanup — making it ideal for transient state that should never be persisted.”
}
},
{
“@type”: “Question”,
“name”: “How does a typing indicator handle abrupt client disconnections?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “The Redis key typing:{conversation_id}:{user_id} has a TTL of 5 seconds. If the client disconnects without sending a typing_stop event, the key expires automatically, triggering a keyspace notification that broadcasts a stopped-typing event to other participants.”
}
},
{
“@type”: “Question”,
“name”: “How is typing indicator spam prevented?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Server-side rate limiting enforces a maximum of one typing_start event per user per 2 seconds. Events beyond this rate are dropped, preventing misbehaving clients from flooding the broadcast channel.”
}
},
{
“@type”: “Question”,
“name”: “How do you show multiple users typing in a group chat?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “The client accumulates typing events from all participants. The server can also scan Redis keys matching typing:{conv_id}:* to return the full list of currently active typers. The UI aggregates these into a message like 'Alice and Bob are typing…'”
}
}
]
}
See also: Meta Interview Guide 2026: Facebook, Instagram, WhatsApp Engineering
See also: Atlassian Interview Guide