Why fetching from blockchain hurt — the getAllVoters() disaster
What happens when direct contract reads scale from 10 items to 1,000. RPC timeouts, silent failures, and the moment you realize blockchain is not a database.
The ChainElect voting dashboard worked beautifully at 5 candidates and 30 voters.
Then I added 1,000 test voters to simulate a real election. I opened the admin panel. The page loaded. The browser spun. And spun. And spun.
12 seconds later: the page loaded. With data from 4 seconds ago.
Then I tried it on a phone on a slow 4G connection. The browser timeout fired. The page showed a blank white screen with no error message.
"Wait. This worked yesterday. What changed?"
Nothing changed in my code. The number of voters changed. That was enough to break everything.
1. The Story: 1,000 Voters, 1,000 RPC Calls
In ChainElect v0.1, the voter list was fetched like this:
At 30 voters: 30 RPC calls. Fine. At 300 voters: 300 RPC calls. Slow. At 1,000 voters: 1,000 RPC calls fired in parallel to the same Polygon Amoy RPC endpoint.
The RPC node responded to the first ~200. Then it started rate-limiting. Then some calls timed out. Then Promise.all rejected the moment any single call failed. The entire voter list load crashed.
2. The Hidden Chain Read Loop
[!TIP] VISUAL TRIGGER FOR FRONTEND: Animate this as a fan-out diagram where the single admin load spawns 1,000 parallel arrows hitting the RPC node. Show the node visually "overloading" as the arrows pile up, then some turning red (timeouts) and the entire Promise.all collapsing. This is the exact moment the naive architecture breaks.

3. Technical Explanation: Why Blockchain Reads Don't Scale Like This
Every .call() in Web3.js or ethers.js is an HTTP request to an RPC node. RPC nodes have:
Rate Limits: Free-tier Alchemy and QuickNode nodes cap concurrent requests. On Polygon Amoy's public RPC (rpc-amoy.polygon.technology), aggressive batching causes HTTP 429 errors.
No Connection Pooling: Unlike PostgreSQL, which maintains persistent database connections, RPC nodes handle each eth_call as a stateless HTTP request with no connection reuse optimization.
Sequential State: The EVM state is a sequential ledger. To read 1,000 accounts, you need 1,000 individual eth_call operations — there is no SELECT * FROM voters equivalent that the node can execute in O(1).
The mathematical reality:
4. The Immediate Bandaid: Multicall
Before the proper solution (event indexing), there is a short-term fix: Multicall.
Multicall is a smart contract deployed on every major network that accepts a batch of eth_call requests and executes them all in a single RPC roundtrip:
1,000 individual RPC calls become 1 batched RPC call. Load time drops from 12 seconds to ~800ms.
But Multicall is still a bandaid. If you need to paginate, filter, or sort — you still have a problem. The real solution is indexing, which we cover in Module M2.5.
The "1,000 RPC call" problem is not unique to your project. It's the standard hitting point for every Web3 developer. The moment your app needs to display a list of more than ~50 on-chain items, you need an indexing strategy. This is why The Graph, Subgraph Studio, and Goldsky exist — they pre-index chain events so your frontend can query them like a traditional SQL database.
The Promise.all Failure Mode:
Using Promise.all for RPC calls seems clever (parallel = faster) but creates a brittle system: if any single call fails, the entire batch fails. In production, use Promise.allSettled instead, which resolves with each result's status (fulfilled or rejected) individually. This way, one RPC timeout doesn't blank the entire voter list.
In ChainElect's Admin.jsx, find where the voter list is loaded. Count the number of individual RPC calls triggered per page load. Now calculate: if Polygon Amoy's public RPC has a rate limit of 10 requests per second, how long would loading 500 voters take sequentially? What does this tell you about relying on public RPC nodes for production UIs?
Was this lesson helpful?
Let us know what you think of this specification. (submitting anonymously)
