How Vyper’s Bounded Loops Keep Gas Costs Predictable
Learn how Vyper’s language design forces loops to have compile‑time known limits, preventing out‑of‑gas attacks and making contract gas costs predictable.
19 Feb 2026, 14:42 UTC

When a Solidity function loops over a dynamic array that can grow indefinitely—such as a list of all users who have ever interacted with a protocol—the gas required to execute that function rises with the array length. Eventually the required gas exceeds the block limit, causing the transaction to revert with an out‑of‑gas error. Once this happens the function becomes permanently uncallable, locking any funds or state that depend on it.
How Vyper Guarantees Bounded Loops
Vyper removes the ability to write loops whose iteration count depends on runtime data. Every for loop must have a limit that the compiler can evaluate as a constant or an immutable value. This makes the maximum number of iterations known at compile time, so the worst‑case gas consumption can be calculated before deployment.
Worked Example: Fixed‑Size Address Registry
# Vyper 0.3.x
MAX_USERS: constant(uint256) = 100
users: public(address[MAX_USERS])
user_count: public(uint256)
@external
def add_user(user: address):
assert self.user_count < MAX_USERS, "Registry full"
self.users[self.user_count] = user
self.user_count += 1
@external
@view
def is_authorized(user: address) -> bool:
for i in range(MAX_USERS):
if self.users[i] == user:
return True
return False
Verification and Risks
- Where to run: compile with
vyper contract.vyusing the Vyper CLI (version ≥0.3.0) or paste into Remix. - Expected check: changing the loop to
range(self.user_count)triggers a compile‑time error becauseself.user_countis a state variable, not a constant. - Risk: a high
MAX_USERSreserves many storage slots, increasing deployment gas; a low limit may cause the registry to reject new users once the bound is reached. - Practical check: after deploying, repeatedly call
add_useruntil it reverts with the message "Registry full" to confirm the bound is enforced.
Trade‑off: Flexibility vs. Safety
Because Vyper cannot iterate over an unknown number of elements, clearing a large list or performing batch operations requires a different pattern. Developers typically use pagination: a function accepts an offset and a limit, processes only that slice, and the caller repeats the call with the next offset. This keeps each transaction within the block gas limit while still allowing the contract to handle arbitrarily many users over time.
Actionable Closing
When moving to Vyper, treat arrays as fixed‑size buffers and design loops around compile‑time constants. The resulting contracts are easier to audit, have predictable gas costs, and resist denial‑of‑service attacks that stem from unbounded growth.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.