Real-time Chat App

If you've ever been asked to "build a chat app" in a machine coding round, you know it's more than just sending messages. It's about real-time updates, state management, and performance under load.

In this article, we'll walk through building a real-time chat app step by step, from local state handling to WebSocket integration, with code examples that are interview-ready and practical.

Step 1: Set Up the UI

A chat app needs two core components:

  1. Message list
  2. Input box

Here's a minimal React setup:

function ChatApp() {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState("");

const sendMessage = () => {
if (!input.trim()) return;
setMessages([
...messages,
{ text: input, sender: "You", timestamp: Date.now() },
]);
setInput("");
};

return (
<div className="chat-container">
<div className="message-list">
{messages.map((msg, idx) => (
<div key={idx} className="message">
<strong>{msg.sender}:</strong> {msg.text}
</div>
))}
</div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && sendMessage()}
placeholder="Type a message..."
/>
</div>
);
}

You now have a basic local chat. Next, let's make it real-time across users.

Step 2: Integrate WebSockets for Real-time Messaging

WebSockets let you push updates to all connected clients without polling.

Here's a minimal Node.js server using ws:

// server.js
import { WebSocketServer } from "ws";

const wss = new WebSocketServer({ port: 8080 });

wss.on("connection", (ws) => {
ws.on("message", (message) => {
wss.clients.forEach((client) => {
if (client !== ws && client.readyState === 1) {
client.send(message);
}
});
});
});

Client-side WebSocket Integration

const socket = useMemo(() => new WebSocket("ws://localhost:8080"), []);

useEffect(() => {
socket.onmessage = (event) => {
const msg = JSON.parse(event.data);
setMessages((prev) => [...prev, msg]);
};
}, []);

const sendMessage = () => {
if (!input.trim()) return;
const msg = { text: input, sender: "You", timestamp: Date.now() };
setMessages((prev) => [...prev, msg]);
socket.send(JSON.stringify(msg));
setInput("");
};

Now messages appear in real-time for all connected users.

Step 3: Handle Multiple Users

  • Add a username prompt when a user joins.
  • Include sender in message payload to differentiate users.
const [username, setUsername] = useState("");

if (!username) {
return (
<input
placeholder="Enter your username"
onKeyDown={(e) =>
e.key === "Enter" && setUsername(e.target.value)
}
/>
);
}
  • WebSocket messages now include the sender's name.
  • The UI renders messages differently depending on who sent them.

Step 4: Optimize Performance for Long Chat Logs

1. Virtualize variable-height messages

  • Messages vary in height (line breaks, emojis, images).
  • Use react-window's VariableSizeList with dynamic sizing:
import { VariableSizeList as List } from "react-window";
import { useRef } from "react";

const sizeMap = useRef(new Map());
const getItemSize = (index) => sizeMap.current.get(index) || 50;
const setItemSize = (index, size) => sizeMap.current.set(index, size);

<List
height={400}
width={300}
itemCount={messages.length}
itemSize={getItemSize}
>
{({ index, style }) => (
<div
style={style}
ref={(el) => {
if (el) setItemSize(index, el.getBoundingClientRect().height);
}}
>
<strong>{messages[index].sender}:</strong> {messages[index].text}
</div>
)}
</List>

This ensures each message has enough vertical space, even with variable content.

2. Batch incoming WebSocket messages

  • Throttle updates to prevent state thrashing:
const buffer = useRef([]);

socket.onmessage = (event) => {
buffer.current.push(JSON.parse(event.data));
};

useEffect(() => {
const interval = setInterval(() => {
if (buffer.current.length) {
setMessages((prev) => [...prev, ...buffer.current]);
buffer.current = [];
}
}, 16); // ~60fps
return () => clearInterval(interval);
}, []);
  • Reduces excessive renders during high traffic.

Step 5: Add Optional Features (Interview Plus Points)

  • Typing indicators (user is typing...)
  • Read receipts
  • Message reactions or emojis
  • Persistent storage (Redis, MongoDB, Firebase)

Even mentioning these in an interview demonstrates end-to-end real-time system design understanding.

Wrapping Up

Building a real-time chat app in a machine coding round tests:

  1. State management: local and remote updates
  2. Real-time communication: WebSocket integration
  3. Performance optimizations: batching, throttling, variable-height virtualization
  4. Scalable architecture thinking: multiple users, persistence

If you can implement this in 45–60 minutes with a clean, functional UI, you're nailing the machine coding round.

This app mirrors real-world features in Slack, Discord, or WhatsApp Web, exactly what interviewers look for in practical coding exercises.