Socket.IO Rooms: Targeted Broadcasting and Cleanup
Socket.IO rooms let you broadcast to specific groups of connected clients, with automatic cleanup when sockets disconnect. Learn how to use them safely and verify behavior.
25 Jul 2025, 23:30 UTC

The Problem: Sending Events to the Right Subset of Users
You have a live dashboard where operators should see alerts for their region only, not every region. Socket.IO broadcasts to all connected clients by default, so you need a way to group sockets and emit to just one group without managing a manual list of socket IDs.
How Rooms Handle Membership and Cleanup
A room is a named group tracked by the Socket.IO server adapter. A socket joins with socket.join(roomId), and the server emits to members with io.to(roomId).emit(event, data). When a socket disconnects, the adapter removes it from every room it joined. Empty room names can remain in the adapter until the process restarts, so high churn applications should monitor room count.
Rooms are namespace-scoped. A room named alerts in /admin is separate from alerts in /public, so the same string can mean different things in different namespaces.
Worked Example: Region-Scoped Alerts
Run this on the server. It assumes you have an HTTP server already created and attached to Socket.IO.
const { Server } = require('socket.io');
const io = new Server(httpServer, { path: '/socket.io' });
const alerts = io.of('/alerts');
alerts.on('connection', (socket) => {
socket.on('subscribe', (region) => {
if (typeof region !== 'string' || region.length === 0) {
return socket.emit('error', { message: 'Invalid region' });
}
socket.join(region);
socket.region = region;
});
socket.on('unsubscribe', () => {
if (socket.region) {
socket.leave(socket.region);
delete socket.region;
}
});
socket.on('disconnect', () => {
// Adapter removes socket from all rooms automatically.
// socket.region is just application bookkeeping.
});
});
// Send to every socket subscribed to 'eu-west'
alerts.to('eu-west').emit('incident', { id: 123, severity: 'high' });
// Debug: inspect active room count in this namespace
setInterval(() => {
console.log('Rooms:', alerts.adapter.rooms.size);
}, 30000);
Client-side, the socket emits subscribe with a region string after connecting. Only sockets that joined eu-west receive the incident event.
Trade-Offs and Limitations
- Empty rooms persist. Disconnected sockets leave rooms automatically, but the room name stays in the adapter. If you create short-lived rooms for every user session, the room registry can grow.
- No validation. Room names are strings. Sanitize user-supplied values before using them as room IDs to avoid injection or enumeration.
- Single-server memory. Rooms live in the server adapter. For multiple Socket.IO nodes, use the Redis adapter so room membership is shared.
Verify the Behavior
- Start the server and connect two clients to
/alerts. - Have both join
eu-west, then emit to that room and confirm both receive it. - Disconnect one client and emit again; confirm only the remaining client receives it.
- Check
alerts.adapter.rooms.sizebefore and after disconnections to confirm sockets leave, then decide whether you need manual cleanup for empty room names.
Rooms are useful for targeted broadcasting when membership changes frequently. Treat the adapter as the source of truth for room membership, sanitize inputs, and monitor room count if your application creates many temporary rooms.