CryptoScope: real-time cryptocurrency rates from multiple exchanges
For CryptoScope, we built a real-time aggregation of cryptocurrency rates: WebSocket connections to multiple exchanges, data normalization, and a frontend that stays smooth even with updates every second.

For CryptoScope, we built a real-time aggregation of rates from several crypto exchanges: WebSocket connections, a unified data format, and a frontend that doesn’t slow down when updates arrive every second.
The task sounded simple: show current rates and let users compare spreads between venues. In practice, systems like this quickly run into two layers of problems. The first is that data comes from different sources and can’t be dropped into a single table as-is. The second is that even when the stream is already assembled, the UI starts to suffer from too-frequent updates.
Before: the data was there, but comparisons weren’t
Before we built the shared schema, each exchange effectively lived on its own. Each venue has its own message format, its own field names, and its own update rules. To a person, that looks like “BTC has a rate here and there.” To code, it’s several different data contracts that need to be brought to the same shape.
CryptoScope was not meant to be just a collection of exchange widgets. Its main value is comparative spread analysis across venues. That means the data has to be comparable: one format for the instrument, the price, the update time, and the source.
So the first step was to build a normalization layer. Everything coming in through the exchanges’ WebSocket connections is mapped to a single structure. From there, the app no longer works with a “message from exchange A” or a “message from exchange B,” but with CryptoScope’s internal format.
That makes it easier to calculate differences between venues and easier to expand the list of sources: a new exchange adds an adapter instead of changing the entire analysis logic.
The real-time stream
To fetch rates, we set up WebSocket connections to several exchanges. That’s a good fit for a task where prices need to update without the client making manual requests.
The stream works like this:
- connect to rate sources;
- receive updates in real time;
- normalize messages into a common format;
- pass normalized data on for display and spread comparison.
Rates are updated every second. That’s not an extreme frequency for the backend, but it’s already enough for careless rendering to become noticeable on the frontend. Especially when there are many rows on screen and the user expects a stable table of values, not an animation.
A practical detail from the project: the most contentious part was not the WebSocket itself, but how often to push changes to the UI. We wanted to show the market as fresh as possible, but the frontend should not rerender everything just because another data packet arrived.
How we kept the UI from stuttering
On the frontend, we used three things: virtualization, debounce, and memoization.
Virtualization is needed so we don’t keep every row in the DOM when the user can only see part of the list. That reduces rendering load and helps the table stay responsive.
Debounce smooths out frequent updates. Data can arrive regularly, but the UI doesn’t always need to react to every micro-event with a separate rerender. In CryptoScope’s case, the balance matters: keep the real-time feel without turning the screen into a source of unnecessary render cycles.
Memoization covers another source of wasted work: repeated calculations and rerenders where the input data for a specific component hasn’t changed. For rate tables, that’s especially noticeable because some values update while others stay the same.
As a result, the frontend receives second-by-second updates but doesn’t try to rebuild the entire screen every time. Only the parts that actually received new data change.
After: a single stream for spread analysis
After these changes, CryptoScope works as a shared layer between multiple exchanges and the analysis UI:
- rates arrive over WebSocket in real time;
- updates reach the frontend every second;
- data is normalized into a single format;
- the UI uses virtualization, debounce, and memoization;
- the user compares spreads between venues in one view.
The main result is that we separated the noise of external sources from the product’s internal model. Exchanges can send data in different ways, but inside CryptoScope there is one clear format, and that is enough to build rate and spread comparisons.