I started with the database and the interviewer immediately steered me toward the transport layer.
Start by clarifying functional and non-functional requirements, then sketch a high-level architecture with separate services for connection management, message routing, storage, and receipts. Dive into the critical path for live delivery and message history, discussing trade-offs around consistency, latency, and scalability.
Pro tip: Emphasize idempotency and ordering guarantees for message delivery, as these are often overlooked but critical for a reliable messaging system. Also, proactively discuss how to handle offline users and message synchronization across multiple devices.
Ask about expected user scale, message volume, latency requirements, and consistency needs. Define functional requirements: one-to-one and group messaging, live delivery, history, and receipts.
Outline core components: WebSocket gateways for persistent connections, a message service for routing and persistence, a presence service, and a receipt service. Use a message queue for asynchronous processing.
Explain how to maintain WebSocket connections, handle reconnections, and route messages to online users. Discuss using a consistent hashing or a registry to map user IDs to gateway servers.
Choose a database (e.g., Cassandra for scalability) and design schema for messages, conversations, and user inboxes. Discuss indexing for efficient history retrieval and pagination.
Describe how to track and propagate receipts: use message IDs, store receipt status per user, and push updates via WebSocket. Ensure idempotency and handle offline users with queuing.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.
Start by clarifying the scope and assumptions (e.g., messaging service, offline storage, delivery guarantees). Then walk through the end-to-end flow: message ingestion, persistence, offline detection, notification, and eventual delivery upon reconnection. Emphasize reliability, idempotency, and ordering.
Pro tip: Highlight the trade-offs between different delivery guarantees (at-least-once vs exactly-once) and how you'd handle duplicate messages or out-of-order delivery. This shows you think beyond the happy path.
Ask about the messaging system's expected scale, delivery guarantees, and whether messages should be stored server-side or client-side. Assume a typical chat system with server-side storage and push notifications.
Describe how A's message is sent to the server, validated, and stored in a durable message store (e.g., database) with metadata like sender, recipient, timestamp, and status.
Explain how the server detects B is offline (e.g., no active connection) and triggers a push notification to B's device, while keeping the message queued for delivery.
When B reconnects, the client authenticates, syncs with the server (e.g., via a sync API or websocket), and fetches undelivered messages. The server marks messages as delivered and updates status.
B's client acknowledges receipt, and the server updates the message status to 'delivered' or 'read'. Discuss idempotency to handle retries and ensure no duplicates.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.
Start by clarifying the workload's communication patterns (bidirectional, low-latency, high-frequency) and then compare WebSocket, SSE, and long polling against those requirements. Defend WebSocket as the best fit for full-duplex, real-time use cases, but acknowledge that SSE or long polling can be preferable when the constraints differ (e.g., unidirectional updates, legacy infrastructure, or simplicity).
Pro tip: Show maturity by quantifying trade-offs (e.g., connection overhead, latency, scalability) and mentioning real-world constraints like proxy/firewall compatibility, which often drive the choice at companies like Uber.
Identify the key characteristics: bidirectional communication, message frequency, latency sensitivity, number of concurrent connections, and client diversity. This sets the criteria for evaluation.
Highlight WebSocket's full-duplex, low-latency nature; SSE's unidirectional server push over HTTP; and long polling's request-response emulation. Discuss overhead, complexity, and scalability for each.
Argue why WebSocket is optimal: it eliminates polling overhead, supports real-time bidirectional messaging, and scales well with proper infrastructure. Tie this to the workload's needs (e.g., live tracking, chat).
Explain scenarios: SSE for unidirectional server-to-client updates (e.g., notifications, feeds) with automatic reconnection; long polling for legacy environments or when WebSocket/SSE are blocked by proxies/firewalls.
Reiterate that the choice depends on trade-offs, and show awareness that even at Uber, different services may use different technologies based on specific constraints.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.
Client-generated idempotency keys plus a monotonic sequence number per conversation.
Start by clarifying the requirements and constraints, such as the expected message volume, ordering guarantees, and deduplication window. Then propose a solution that combines client-generated sequence numbers and unique message IDs with server-side ordering and deduplication mechanisms, and discuss trade-offs between consistency and availability.
Pro tip: Emphasize idempotency and exactly-once semantics, and mention how you would handle edge cases like out-of-order delivery and client retries with exponential backoff. Also, relate it to real-world systems like Uber's chat or driver-rider communication.
Ask about the scale, ordering scope (per-conversation), deduplication window, and consistency requirements. Understand the mobile client's behavior and network conditions.
Propose that the client assigns a monotonically increasing sequence number per conversation and a globally unique message ID (e.g., UUID) to each message. Include retry logic with exponential backoff and jitter.
Use the sequence number to order messages per conversation, and the message ID to deduplicate. Store recent message IDs in a cache (e.g., Redis) with TTL for deduplication, and use a persistent store for ordering.
Discuss how to handle out-of-order messages (e.g., buffer and reorder), missing sequence numbers, and client reconnections. Consider using a message queue or stream processing for scalability.
Compare approaches like using a centralized sequencer vs. client-side sequencing, and discuss consistency vs. availability trade-offs. Mention how to scale and monitor the solution.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.
Fan-out on write means you copy the message to every member's inbox at send time, which is fast for reads but expensive at write time for large groups.
Start by clarifying the use case and requirements (e.g., latency, consistency, cost) for a 200-member group. Then compare fan-out on write vs. fan-out on read, highlighting trade-offs in terms of write/read amplification, storage, and complexity. Conclude with a recommendation based on typical patterns at scale, such as a hybrid approach.
Pro tip: Mention that the optimal choice depends on the read/write ratio and latency requirements, and that a hybrid approach (e.g., fan-out on write for active users, on read for inactive) is often used in production systems like chat apps.
Ask about the expected read/write ratio, latency SLAs, consistency needs, and group activity patterns (e.g., are all 200 members active?).
Explain that on write, the message is immediately delivered to all 200 members' inboxes, resulting in 200 writes per message.
Explain that on read, the message is stored once and each member pulls it when they open the app, resulting in 200 reads per message.
Discuss write amplification vs. read amplification, storage costs, latency, and complexity for each approach.
Based on the requirements, suggest a suitable approach or a hybrid solution, and justify your choice.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.
You need a presence registry, basically a key-value store mapping user ID to the connection server they're currently on.
Start by framing the problem as two distinct challenges: scaling the stateful connection tier and routing messages to the correct server. Then, propose a solution using a distributed registry (e.g., Redis or a custom service) to map user IDs to server instances, and discuss scaling strategies like consistent hashing and connection draining.
Pro tip: Emphasize the trade-offs between consistency and availability in the registry, and mention how you would handle server failures gracefully with reconnection logic and session resumption.
Ask about scale (number of concurrent connections, messages per second), latency requirements, and consistency needs. This shows you think before designing.
Explain how to scale horizontally by adding more connection servers, using a load balancer with consistent hashing to distribute connections evenly and minimize rebalancing.
Propose a centralized or distributed registry (e.g., Redis, ZooKeeper) that maps user IDs to the server holding their socket. Discuss how to keep it updated on connect/disconnect.
Describe the message flow: when a message arrives, look up the recipient's server in the registry, then forward the message via an internal RPC or message queue.
Discuss how to handle server crashes (e.g., registry cleanup, client reconnection) and scaling (e.g., connection draining, consistent hashing rebalancing).
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.