# Handling 1000+ IoT Devices: Scaling Past the Demo
By Govind Bajaj · 2025

Tags: iot, scaling, react, performance, mongodb
My IoT dashboard worked beautifully with 10 devices. At 200, it lagged. At 1000, it crashed. Here's how I scaled the architecture to handle real-world device counts.
My IoT dashboard worked beautifully with ten devices.

At two hundred devices, the dashboard started lagging. At one thousand devices, the browser tab crashed.

The problem was not the MQTT broker or the database. It was the frontend. I was rendering a thousand DOM elements, each updating every five seconds. React was re-rendering the entire list on every incoming message.

Here is how I scaled the architecture to handle real-world device counts.

## Problem 1: Too Many DOM Elements

Rendering one thousand sensor cards creates one thousand DOM nodes. The browser struggles with layout calculations on every update.

Solution: Virtualize the list. Only render visible elements.

```tsx
import { FixedSizeGrid as Grid } from "react-window"

function SensorGrid({ sensors }) {
  return (
    <Grid
      columnCount={4}
      columnWidth={250}
      height={800}
      rowCount={Math.ceil(sensors.length / 4)}
      rowHeight={120}
      width={1000}
    >
      {({ columnIndex, rowIndex, style }) => {
        const index = rowIndex * 4 + columnIndex
        const sensor = sensors[index]
        if (!sensor) return null
        return <div style={style}><SensorCard sensor={sensor} /></div>
      }}
    </Grid>
  )
}
```

With react-window, only the visible sensors render — about twenty at a time. Scroll performance stays smooth regardless of total sensor count.

## Problem 2: Too Many Re-renders

Every incoming sensor message triggered a state update, which re-rendered the entire list. With one thousand sensors publishing every five seconds, that is two hundred re-renders per second.

Solution: Normalize state and update individual sensors.

```tsx
const [sensorMap, setSensorMap] = useState(new Map())

useEffect(() => {
  socket.on("reading", (update) => {
    setSensorMap(prev => {
      const next = new Map(prev)
      next.set(update.sensorId, { ...prev.get(update.sensorId), ...update })
      return next
    })
  })
}, [])
```

Using a Map with individual updates prevents React from re-rendering unchanged sensors.

## Problem 3: Too Many WebSocket Messages

One thousand sensors sending one reading per second is one thousand WebSocket messages per second to every connected client.

Solution: Server-side aggregation.

Instead of forwarding every reading, the backend aggregates and sends summaries every 5 seconds. Detailed readings are available on demand when a user clicks a zone.

## Problem 4: Database Write Bottleneck

One thousand inserts per second overwhelms a single MongoDB instance.

Solution: Time-series collection and downsampling.

MongoDB 5+ supports time-series collections, which are optimized for timestamped data. They compress data by 70-90% and improve query performance.

For historical data, downsample to per-minute averages after 24 hours.

## Takeaways

- Virtualize long lists — only render visible DOM elements
- Normalize state with Maps to prevent unnecessary re-renders
- Aggregate data server-side before sending to clients
- Use MongoDB time-series collections for efficient sensor data storage
- Downsample historical data to per-minute or per-hour averages
- The frontend is usually the bottleneck, not the backend or database
- Test with realistic device counts early — demo-scale testing hides real problems