DEV Community

Cover image for WebSockets: How Real-Time Communication Works
Tanu Priya
Tanu Priya

Posted on

WebSockets: How Real-Time Communication Works

Imagine you're using a chat application.

You send:

"Hey, are you free?"

And almost instantly, the other person sees it.

There is no page refresh.

You don't click a button to check for new messages.

The message simply appears.

Now think about a few other applications:

  • A stock-trading dashboard where prices continuously change
  • A multiplayer game where player movements need to be synchronized
  • A delivery app where you can watch a driver move on a map
  • A collaborative editor where another user's changes appear immediately
  • A notification system that pushes updates as soon as something happens

These applications have something important in common:

The server needs to communicate with the client without waiting for the client to make another request.

This is where WebSockets become useful.


What Are WebSockets?

WebSockets provide a persistent, two-way communication channel between a client and a server.

With traditional HTTP, communication usually looks like this:

Client
   |
   | HTTP Request
   ↓
Server
   |
   | HTTP Response
   ↓
Client
Enter fullscreen mode Exit fullscreen mode

The client makes a request, and the server sends a response.

With WebSockets, the relationship changes:

Client  ←────────────────→  Server
             Persistent
             Connection
Enter fullscreen mode Exit fullscreen mode

Once the connection is established, both sides can send messages whenever they need to.

The server doesn't have to wait for the client to make another HTTP request.

That's the fundamental reason WebSockets are useful for real-time applications.


Why Isn't Regular HTTP Enough?

Suppose you're building a chat application using traditional HTTP.

The client could periodically ask:

GET /messages
Enter fullscreen mode Exit fullscreen mode

The server might respond:

[
  {
    "from": "Alex",
    "message": "Hello"
  }
]
Enter fullscreen mode Exit fullscreen mode

But what happens when a new message arrives?

The client doesn't automatically know.

So it has to ask again:

Client → "Any new messages?"
Server → "No"

Client → "Any new messages?"
Server → "No"

Client → "Any new messages?"
Server → "Yes"
Enter fullscreen mode Exit fullscreen mode

This approach is called polling.

It works, but it has an obvious inefficiency:

The client keeps making requests even when nothing has changed.


Polling vs WebSockets

With polling:

Client → Request
Server → Response

Client → Request
Server → Response

Client → Request
Server → Response
Enter fullscreen mode Exit fullscreen mode

The client repeatedly asks:

"Is there anything new?"

With WebSockets:

Client ←────────────────→ Server
         Persistent
         Connection
Enter fullscreen mode Exit fullscreen mode

Once the connection is established, the server can push an update immediately.

For a chat application:

User sends message
        ↓
Server receives it
        ↓
Server processes it
        ↓
Server pushes message
        ↓
Connected clients receive it
Enter fullscreen mode Exit fullscreen mode

This avoids repeatedly asking the server whether something has changed.


How Does a WebSocket Connection Start?

A WebSocket connection doesn't simply appear.

It begins with a handshake.

Conceptually:

Client
   |
   | Connection Request
   ↓
Server
   |
   | WebSocket Upgrade
   ↓
Client
   |
   | Persistent Connection
   ↓
Server
Enter fullscreen mode Exit fullscreen mode

The initial handshake uses HTTP.

If the server accepts the upgrade, the connection switches to the WebSocket protocol.

After that, communication happens over the established WebSocket connection.

So the flow changes from:

HTTP

Request → Response
Request → Response
Request → Response
Enter fullscreen mode Exit fullscreen mode

to:

WebSocket

Client ←────────────────→ Server
Enter fullscreen mode Exit fullscreen mode

Now either side can send messages.


The WebSocket Lifecycle

A useful way to understand WebSockets is to think about their lifecycle:

CONNECTING
    ↓
   OPEN
    ↓
MESSAGE EXCHANGE
    ↓
 CLOSING
    ↓
  CLOSED
Enter fullscreen mode Exit fullscreen mode

1. Connecting

The client is attempting to establish the connection.

2. Open

The handshake succeeds and the connection becomes active.

3. Message Exchange

Both client and server can send messages.

Client → Server
Server → Client
Client → Server
Server → Client
Enter fullscreen mode Exit fullscreen mode

This can continue as long as the connection remains open.

4. Closing

Either side can initiate the closing process.

5. Closed

The connection no longer exists.

If real-time communication is still required, the application may need to establish another connection.


Sending Messages with JavaScript

The browser provides a built-in WebSocket API.

A simple client could look like this:

const socket = new WebSocket(
  "wss://example.com/socket"
);

socket.onopen = () => {
  socket.send(
    JSON.stringify({
      type: "CHAT_MESSAGE",
      message: "Hello!"
    })
  );
};

socket.onmessage = (event) => {
  console.log("Received:", event.data);
};

socket.onerror = (error) => {
  console.error("WebSocket error:", error);
};

socket.onclose = () => {
  console.log("Disconnected");
};
Enter fullscreen mode Exit fullscreen mode

The important events are:

onopen

The connection has been established.

onmessage

A message has been received from the server.

onerror

Something went wrong with the connection.

onclose

The connection has been closed.

The API is relatively simple.

Scaling and managing the connection is where things become interesting.


The Real Challenge: Connection Management

Opening one WebSocket connection is easy.

Managing thousands or millions of them is a completely different problem.

Imagine:

1,000 connected users
Enter fullscreen mode Exit fullscreen mode

That's manageable for many systems.

Now imagine:

100,000 concurrent connections
Enter fullscreen mode Exit fullscreen mode

Or:

1,000,000 concurrent connections
Enter fullscreen mode Exit fullscreen mode

Suddenly, your architecture needs to consider:

  • Connection limits
  • Memory usage
  • CPU usage
  • Network bandwidth
  • Authentication
  • Heartbeats
  • Reconnection
  • Load balancing
  • Connection cleanup
  • Message routing
  • Failure recovery

Unlike a short HTTP request, a WebSocket connection can remain open for a long time.

That changes how infrastructure needs to be designed.


WebSockets and Load Balancing

This connects directly to what we discussed on Day 3.

With a traditional HTTP application, requests can often be distributed like this:

              Load Balancer
             /      |      \
            ↓       ↓       ↓
         Server A Server B Server C
Enter fullscreen mode Exit fullscreen mode

A request might go to Server A, while the next request goes to Server B.

With WebSockets, things are different.

Suppose Alice connects to Server A:

Alice
  ↓
Server A
Enter fullscreen mode Exit fullscreen mode

The WebSocket connection stays attached to Server A.

Now Bob connects to Server B:

Bob
 ↓
Server B
Enter fullscreen mode Exit fullscreen mode

What happens when Alice sends a message to Bob?

Server A knows about Alice's connection.

But Bob's connection exists on Server B.

Server A needs a way to communicate with Server B.

This is where distributed messaging becomes important.


Scaling WebSocket Servers

A common architecture can look like this:

                       Clients
                    /     |     \
                   ↓      ↓      ↓
             ┌──────────────────────┐
             │    Load Balancer     │
             └──────────────────────┘
                   /      |      \
                  ↓       ↓       ↓
               WS 1     WS 2     WS 3
                  \       |       /
                   \      |      /
                    ↓     ↓     ↓
                  Message Broker
Enter fullscreen mode Exit fullscreen mode

The responsibilities are now separated.

WebSocket servers

Maintain client connections.

Message broker

Allows WebSocket servers to exchange events.

Backend services

Handle application logic.

Database

Stores persistent data.

For example:

Alice
  ↓
WebSocket Server 1
  ↓
Message Broker
  ↓
WebSocket Server 2
  ↓
Bob
Enter fullscreen mode Exit fullscreen mode

The WebSocket connection remains local to its server, while the messaging layer allows the servers to communicate.

Technologies such as Redis Pub/Sub, Kafka, or other messaging systems can be used depending on the requirements and architecture.


Broadcasting Messages

Now consider a chat room with ten users.

Alice sends:

"Hello everyone!"

The server may need to broadcast that message to the other users:

                 Message
                    ↓
                  Server
              / / / | \ \ \
             ↓ ↓ ↓  ↓  ↓ ↓ ↓
            U1 U2 U3 U4 U5 U6 U7
Enter fullscreen mode Exit fullscreen mode

For a small room, this is straightforward.

But imagine a system with:

1 million connected users
Enter fullscreen mode Exit fullscreen mode

and thousands of messages arriving every second.

Broadcasting every message to every connected user would be extremely expensive.

This is why large real-time systems need efficient message routing.


Rooms and Channels

One common solution is to organize connections into logical groups.

For example:

Room: cricket-match-123

Users:
Alice
Bob
Charlie
David
Enter fullscreen mode Exit fullscreen mode

When a match update occurs:

Match Update
     ↓
Room: cricket-match-123
     ↓
Connected users in that room
Enter fullscreen mode Exit fullscreen mode

A chat application might use:

Room: conversation-42
Enter fullscreen mode Exit fullscreen mode

Only the participants in that conversation need to receive its messages.

This is much more efficient than broadcasting every event to every connected client.


Authentication Still Matters

A WebSocket connection should not automatically be trusted simply because it was established.

The server still needs to know:

Who is connecting, and what are they allowed to access?

A simplified flow:

Client
  ↓
WebSocket Connection
  ↓
Authentication
  ↓
Valid?
  ├── Yes → Accept
  └── No  → Reject
Enter fullscreen mode Exit fullscreen mode

After authentication, the server can associate the connection with a user:

Connection
    ↓
User ID: 123
    ↓
Rooms:
- conversation-42
- notifications-123
Enter fullscreen mode Exit fullscreen mode

This information can then be used when routing messages.

Authentication and authorization become especially important when a connection can remain open for minutes or hours.


What Happens When the Network Fails?

Real networks are unreliable.

A user might:

  • Lose Wi-Fi
  • Switch from Wi-Fi to mobile data
  • Put their laptop to sleep
  • Close the browser
  • Lose VPN connectivity
  • Move between networks

The server may not immediately know what happened.

This creates the problem of dead connections.

That's why WebSocket systems commonly use heartbeats.


Heartbeats and Ping/Pong

A server can periodically check whether a connection is still alive.

Conceptually:

Server → Ping
Client → Pong
Enter fullscreen mode Exit fullscreen mode

If the client stops responding:

Ping
 ↓
No response
 ↓
Connection considered unhealthy
 ↓
Close connection
Enter fullscreen mode Exit fullscreen mode

This allows the server to clean up stale connections instead of keeping resources allocated forever.


Reconnection

Disconnecting is normal.

The important question is:

What does the application do after the connection is lost?

A simple client might follow:

Connected
   ↓
Connection Lost
   ↓
Wait
   ↓
Reconnect
   ↓
Connected
Enter fullscreen mode Exit fullscreen mode

But immediately reconnecting can create another problem.

Imagine a server crashes while 100,000 clients are connected.

If all 100,000 clients reconnect immediately:

100,000 clients
      ↓
100,000 reconnect attempts
      ↓
Server overload
Enter fullscreen mode Exit fullscreen mode

The recovery process itself can cause another outage.

This is why real systems commonly use reconnection backoff.

For example:

Attempt 1 → Immediately
Attempt 2 → 1 second
Attempt 3 → 2 seconds
Attempt 4 → 4 seconds
Enter fullscreen mode Exit fullscreen mode

Random jitter can also be added so clients don't all reconnect at exactly the same time.


What Happens to Messages During a Disconnect?

Here's a more difficult question.

Suppose Alice loses her connection.

While she's offline, the server receives:

Message A
Message B
Message C
Enter fullscreen mode Exit fullscreen mode

When Alice reconnects, should she receive those messages?

If the answer is yes, the system needs a way to determine what she missed.

One approach is to assign sequence numbers:

101 → Message A
102 → Message B
103 → Message C
Enter fullscreen mode Exit fullscreen mode

When Alice reconnects, she can tell the server:

"The last message I received was 101."

The server can then provide:

102
103
Enter fullscreen mode Exit fullscreen mode

This turns a simple WebSocket connection into a more reliable event-delivery system.

And this is an important distinction:

WebSockets provide a communication channel. They don't automatically provide durable message delivery.

If messages must survive disconnections, your system needs persistence and recovery logic.


WebSockets vs Polling

Polling WebSockets
Connection Repeated HTTP requests Persistent connection
Server push Not directly Yes
Real-time experience Limited Excellent
Implementation Simpler More complex
Connection management Relatively simple Important
Scaling Usually simpler Requires more planning
Best for Infrequent updates Frequent real-time updates

Polling isn't inherently bad.

If data changes only occasionally, polling every few seconds may be completely reasonable.

WebSockets become more attractive when the application needs frequent, low-latency, two-way communication.


WebSockets vs Server-Sent Events

WebSockets aren't the only way to build real-time experiences.

Another option is Server-Sent Events (SSE).

With SSE:

Server
   ↓
   ↓
   ↓
Client
Enter fullscreen mode Exit fullscreen mode

The server can continuously send updates to the client.

With WebSockets:

Client
   ↕
Server
Enter fullscreen mode Exit fullscreen mode

Both sides can communicate.

So:

WebSockets

Good when you need two-way communication.

Examples:

  • Chat
  • Multiplayer games
  • Collaborative applications

SSE

Useful when communication is primarily server → client.

Examples:

  • Live notifications
  • Streaming status updates
  • Real-time dashboards

The right choice depends on the communication pattern.


When Should You Not Use WebSockets?

WebSockets are powerful, but they aren't required for every application.

Suppose your application mostly does:

GET /products
GET /users/123
POST /orders
PUT /profile
DELETE /comment
Enter fullscreen mode Exit fullscreen mode

Traditional HTTP is probably enough.

You don't need a persistent connection simply because the application is modern.

Ask one question:

Does the server need to push updates to the client without waiting for another request?

If not, standard HTTP may be simpler and more appropriate.


A Production-Style WebSocket Architecture

Putting the concepts together:

                         Clients
                       /    |    \
                      ↓     ↓     ↓
                ┌─────────────────────┐
                │    Load Balancer     │
                └──────────┬──────────┘
                           ↓
               ┌───────────┼───────────┐
               ↓           ↓           ↓
            WS Server   WS Server   WS Server
               │           │           │
               └───────────┼───────────┘
                           ↓
                    Message Broker
                           ↓
                    Backend Services
                           ↓
                       Database
Enter fullscreen mode Exit fullscreen mode

Each component has a responsibility:

Load Balancer

Distributes incoming connections.

WebSocket Servers

Maintain client connections.

Message Broker

Allows servers to exchange events.

Backend Services

Handle business logic.

Database

Stores persistent data.

This separation makes it possible to scale different parts of the system independently.


Common WebSocket Mistakes

1. Treating WebSockets like normal HTTP requests

A WebSocket connection remains open, so connection lifecycle and resource usage matter.

2. Ignoring disconnections

Clients will disconnect.

Your system needs a recovery strategy.

3. Broadcasting everything

Sending every event to every client doesn't scale.

Route messages only where they are needed.

4. Forgetting authentication

A persistent connection still needs authentication and authorization.

5. No heartbeat mechanism

Dead connections can consume resources if they're never detected.

6. Reconnecting too aggressively

A large number of simultaneous reconnection attempts can overload a recovering server.

7. Assuming messages are automatically durable

A WebSocket connection is not a message queue or database.

If messages need to survive disconnects, persistence and replay mechanisms are required.


The Bigger System Design Lesson

WebSockets aren't simply:

"A faster version of HTTP."

They solve a different communication problem.

HTTP works well when your application follows:

Client
  ↓
Request
  ↓
Server
  ↓
Response
Enter fullscreen mode Exit fullscreen mode

WebSockets become useful when you need:

Client
  ↕
Persistent Connection
  ↕
Server
Enter fullscreen mode Exit fullscreen mode

The basic protocol is relatively straightforward.

The difficult part is everything around it:

  • Authentication
  • Connection management
  • Heartbeats
  • Reconnection
  • Message routing
  • Load balancing
  • Scaling
  • Failure recovery
  • Message persistence

That's where system design comes in.


Final Takeaway

A WebSocket gives your application a persistent communication channel.

But real-time doesn't automatically mean reliable or scalable.

At small scale, you might only need:

Client
  ↕
WebSocket Server
Enter fullscreen mode Exit fullscreen mode

At larger scale, the architecture may evolve into:

Clients
   ↓
Load Balancer
   ↓
WebSocket Servers
   ↓
Message Broker
   ↓
Backend Services
   ↓
Database
Enter fullscreen mode Exit fullscreen mode

And then you have to think about what happens when:

  • A server crashes
  • A client disconnects
  • Thousands of clients reconnect
  • Messages are missed
  • A WebSocket server needs to communicate with another server
  • Traffic suddenly increases

That's the real lesson from WebSockets:

Building a real-time connection is easy. Building a real-time system that remains reliable as connections, traffic, and failures increase is the real engineering challenge.

Top comments (0)