Episode 3 — NodeJS MongoDB Backend Architecture / 3.15 — Realtime Communication WebSockets

3.15 — Interview Questions: Realtime Communication & WebSockets

Common interview questions on WebSocket, Socket.io, and real-time architecture, ranging from conceptual to system design.


<< Back to README


Conceptual Questions

Q1. What is the WebSocket protocol and how does it differ from HTTP? WebSocket is a full-duplex communication protocol over a single TCP connection. Unlike HTTP's request-response model where the client always initiates, WebSocket allows both client and server to send data independently at any time. The connection is persistent (stays open) and has minimal per-message overhead (~2-6 bytes vs ~800+ bytes for HTTP headers).

Q2. Explain the WebSocket handshake process. The client sends an HTTP GET request with Upgrade: websocket and Connection: Upgrade headers, along with a Sec-WebSocket-Key. The server responds with HTTP 101 (Switching Protocols) and a Sec-WebSocket-Accept header (SHA-1 hash of the key + magic string). After this handshake, the connection upgrades from HTTP to WebSocket protocol.

Q3. Compare short polling, long polling, SSE, and WebSocket. When would you use each?

  • Short polling: Simple, repeated requests at intervals. Use for simple dashboards with infrequent updates.
  • Long polling: Server holds request open until data available. Use when WebSocket is not available and you need lower latency than short polling.
  • SSE: Persistent one-way server-to-client stream. Use for news feeds, log streaming, or any server-push-only scenario.
  • WebSocket: Full bidirectional persistent connection. Use for chat, gaming, collaborative editing, or any scenario needing fast two-way communication.

Q4. What is the difference between ws:// and wss://? ws:// is unencrypted WebSocket (analogous to HTTP), while wss:// uses TLS encryption (analogous to HTTPS). Always use wss:// in production to prevent man-in-the-middle attacks and to work through firewalls/proxies that may block unencrypted WebSocket traffic.

Q5. What does Socket.io add on top of raw WebSocket? Automatic reconnection with exponential backoff, fallback to long polling when WebSocket is unavailable, rooms and namespaces for organizing connections, broadcasting helpers, acknowledgement callbacks, middleware system for auth/logging, automatic binary handling, and multiplexing via namespaces.


Technical Questions

Q6. How do you set up Socket.io with Express? What is the common mistake? Create an HTTP server from the Express app (http.createServer(app)), then pass it to Socket.io (new Server(server)). The common mistake is using app.listen() instead of server.listen() -- app.listen() creates its own internal HTTP server that Socket.io is not attached to.

Q7. Explain rooms vs namespaces in Socket.io. Namespaces are separate communication channels identified by a path (e.g., /chat, /admin). They are like separate apps on the same server, with their own middleware and event handlers. Rooms are groups within a namespace for targeted broadcasting. They are server-side only and created dynamically when sockets join. Use namespaces to separate features; use rooms to group users within a feature.

Q8. How do you authenticate WebSocket connections in Socket.io? Use middleware (io.use()) to intercept connections before they are established. Extract the JWT from socket.handshake.auth.token, verify it, and attach user data to the socket object (socket.user = userData). Call next() to allow the connection or next(new Error('message')) to reject it. On the client, pass the token via the auth option and handle connect_error events.

Q9. How do you handle the "disconnecting" vs "disconnect" events? disconnecting fires before the socket leaves its rooms (so socket.rooms is still populated). disconnect fires after the socket has left all rooms. Use disconnecting when you need to know which rooms the socket was in (e.g., to notify room members). Use disconnect for general cleanup.

Q10. What is the acknowledgement pattern in Socket.io? The sender passes a callback as the last argument to emit(). The receiver's event handler receives this callback and can invoke it with response data. This creates a request-response pattern within the event-based system, useful for confirming operations like message delivery or data saves.


System Design Questions

Q11. Design a real-time notification system for a social media platform. Key components: (1) Socket.io server with JWT auth middleware. (2) Each user joins a personal room (user:<id>) on connection. (3) Notification service (REST or internal) calls io.to('user:<id>').emit() when events occur. (4) Track multiple connections per user (multi-device). (5) Store unread notifications in database. (6) Send unread count on connection. (7) Handle offline users by storing notifications for later delivery. (8) Implement a grace period before marking users offline.

Q12. How would you scale Socket.io to multiple servers? Use the @socket.io/redis-adapter package. All Socket.io instances connect to the same Redis server. When one instance emits to a room, Redis Pub/Sub broadcasts it to all instances, which then forward to their locally connected clients. You also need sticky sessions (via load balancer like Nginx with ip_hash) so the Socket.io handshake completes on the same server.

Q13. Design a real-time collaborative text editor. Architecture: Each document has a room. When users open a document, they join the room. Use Operational Transformation (OT) or CRDTs for conflict resolution. Broadcast deltas (not full content) for efficiency. Show cursor positions of other editors. Handle offline editing with operation queues that sync on reconnect. Use versioning to detect and resolve conflicts.

Q14. How would you implement rate limiting for a WebSocket-based chat? Two levels: (1) Connection-level: limit max connections per IP using middleware (track in a Map, decrement on disconnect). (2) Event-level: track message timestamps per user in a sliding window array. When a user exceeds the limit (e.g., 30 messages/minute), reject the event with a retry-after time. For distributed systems, use Redis to share rate limit state across instances.

Q15. Your WebSocket server is consuming too much memory. How do you diagnose and fix it? Common causes: (1) Event listener leaks (not removing listeners on disconnect). (2) Storing too much data on socket objects. (3) Message history growing unbounded in memory. (4) Not cleaning up rooms/maps on disconnect. Diagnosis: Monitor with process.memoryUsage(), check listener counts with socket.listenerCount(), profile with Node.js heap snapshots. Fixes: clean up in disconnect handlers, use database for message history, set max listeners, implement connection limits.


<< Back to README