Building First Voting System
Designing a naive Solidity voting engine using simple arrays and state allocations.
Let's start with a classic feeling of builder success:
You are sitting in Remix. You want to build a decentralized voting system. You think: Solidity struct is easy! I'll store candidates as a struct with a string name and a uint256 vote count, and put them inside a candidate array. Then I'll iterate through them when someone wants to see the winner. Simple, clear, and perfectly readable!
I genuinely felt like a genius when I wrote this exact architecture early on. My test suite with 3 candidates and 5 votes completed in milliseconds, consuming virtually zero mock gas.
The hard reality is that writing code that compiles is easy; writing code that remains secure and runnable under production scale is the actual challenge.
1. The Metaphor: The Cardboard Bookshelf
To understand why this simple design represents an architectural hazard, imagine building a bookshelf:
- The Shelf (Naive Struct Array): You buy a light, cheap corrugated cardboard bookshelf. You place 5 paperbacks on it. It holds them beautifully, looks neat in your room, and cost you almost nothing to set up.
- The Production Weight (Scale): A giant delivery truck backs up to your house and unloads a complete 30-volume heavy leather encyclopedia. You stack the volumes on the cardboard shelf.
- The Collapse: With a loud rip, the cardboard bends, tears, and the entire shelf collapses into a heap of rubble.
In Solidity, dynamic arrays and heavy on-chain strings are that cardboard shelf. They hold light local test data beautifully, but buckle and collapse under real-world load.
A clean, green local test suite (like Hardhat or Foundry running on a local hardhat node) is not a production validation. Local nodes run with infinite gas ceilings and zero network latency. If you validate your contracts strictly based on "it worked in local testing," you are setting yourself up for live network disaster!

2. The Naive Code: The Ticking Time Bomb
Let's look at the exact code of our naive, first-version voting system. This code compiles cleanly and works perfectly for a small test group:
Can you trace why this code compiles but represents a major hazard?
string name: Storing candidate names directly inside state arrays requires dynamic memory expansion opcodes, draining users' gas.voterRegistry.push: Appending to a dynamic list scales state writes sequentially.tallyTotalVotesLoop: Theforloop iterates O(N) overvoterRegistry. When 1,000 users have voted, this check will hit the block gas limit, permanently freezing the total count query!
The View Loop Trap: Many beginners assume that since tallyTotalVotes is a view function, the loop has no gas cost. While external client calls are free, if another write transaction inside your system ever calls tallyTotalVotes internally during state updates, it will charge the full loop execution gas fee, bricking the transaction!
3. Summary of Naive Hazards
- O(N) Scaling: The cost to execute queries scales linearly with the number of participants.
- On-Chain Text Storing: Wastes precious storage slots.
- Unbounded State Mutations: Increases gas overhead on every transaction.
Deploy the NaiveVoting contract in Remix. Add 2 candidates. Call vote once and look at the transaction gas cost. In next lesson, we will see what happens when the voter list grows to hundreds of active participants and why the system completely collapses!
Was this lesson helpful?
Let us know what you think of this specification. (submitting anonymously)
