5/5
## Mastering For Loops in Vyper: Iteration and State Resets When building smart contracts, managing state effectively is crucial. This includes not only storing and updating data but also resetting it when necessary, such as clearing records after a process is complete. In Vyper, different data structures offer varying levels of ease for these operations, particularly when comparing dynamic arrays (`DynArray`) and mappings (`HashMap`). This lesson explores how `for` loops become essential tools for handling these scenarios, focusing on the common challenge of resetting mappings. ### The Challenge: Resetting Arrays vs. Mappings Let's consider a scenario like our `buy_me_a_coffee.vy` contract, where we track who has funded the contract and how much they've contributed. We might use two state variables: 1. `funders: DynArray[address, N]` - A dynamic array storing the addresses of everyone who has sent funds. 2. `funder_to_amount_funded: HashMap[address, uint256]` - A mapping linking each funder's address to the total amount they've funded. When a user funds the contract, we update both: ```vyper # Inside the fund() function self.funders.append(msg.sender) self.funder_to_amount_funded[msg.sender] += msg.value ``` Now, imagine a `withdraw()` function where the contract owner retrieves the funds. After withdrawal, we likely want to reset the state for the next funding cycle. Resetting the `DynArray` is straightforward and gas-efficient: ```vyper # Inside the withdraw() function # Resets the dynamic array to an empty list self.funders = [] ``` However, resetting the `HashMap` is more complex. Unlike arrays, you **cannot** simply assign an empty structure to a storage mapping in Vyper: ```vyper # This is NOT valid Vyper syntax for storage mappings # self.funder_to_amount_funded = {} ``` To clear the recorded amounts in the mapping, we must individually update each relevant key-value pair back to its default state (e.g., setting the `uint256` amount to 0). This requires iterating through the funders whose balances we need to reset. This is where `for` loops become indispensable. This highlights a key design tradeoff: * **Arrays (`DynArray`)**: Easy and cheap to reset entirely. Retrieving a specific value or checking if an element exists often requires iteration (potentially expensive). * **Mappings (`HashMap`)**: Excellent for direct lookups (O(1) complexity). Difficult and potentially very gas-expensive to reset, as it requires iterating through keys and performing individual storage writes for each reset. ### Introducing For Loops in Vyper `for` loops provide the mechanism to execute a block of code repeatedly, making them perfect for iterating over collections or ranges. Vyper supports several ways to structure `for` loops. **1. Iterating over a Range (End Value)** The `range(stop)` function iterates from 0 up to (but not including) the `stop` value. Note the mandatory type annotation for the loop variable (`i: uint256`). ```vyper # Example: Loop.vy @external @pure def for_loop() -> DynArray[uint256, 10]: arr: DynArray[uint256, 10] = [] # Loop from i = 0 up to (not including) 5 for i: uint256 in range(5): arr.append(i) return arr # Output: [0, 1, 2, 3, 4] ``` **2. Iterating over a Range (Start and End Values)** You can specify both a starting and ending point using `range(start, stop)`. The loop includes `start` but excludes `stop`. ```vyper # Example: Loop.vy @external @pure def for_loop_start_end() -> DynArray[uint256, 10]: arr: DynArray[uint256, 10] = [] # Loop from i = 5 up to (not including) 10 for i: uint256 in range(5, 10): arr.append(i) return arr # Output: [5, 6, 7, 8, 9] ``` **3. Iterating Directly Over List Elements** Vyper allows iterating directly over the elements of an array (fixed-size or dynamic). The loop variable takes on the *value* of each element in sequence, not its index. ```vyper # Example: Loop.vy @external @pure def for_loop_list() -> DynArray[uint256, 10]: arr: DynArray[uint256, 10] = [] # NOTE: This example uses a fixed-size list for simplicity. # Iteration works similarly for DynArray state variables. nums: uint256[4] = [11, 22, 33, 44] # Loop through each element VALUE in nums for i: uint256 in nums: arr.append(i) return arr # Output: [11, 22, 33, 44] ``` **4. Controlling Loop Flow: `continue` and `break`** Like many programming languages, Vyper provides keywords to control loop execution: * `continue`: Skips the rest of the code in the current iteration and proceeds to the next one. * `break`: Exits the loop entirely. ```vyper # Example: Loop.vy @external @pure def for_loop_skip() -> DynArray[uint256, 10]: arr: DynArray[uint256, 10] = [] for i: uint256 in range(5): if i == 2: continue # Skip iteration when i is 2 if i == 4: break # Exit loop when i is 4 arr.append(i) return arr # Output: [0, 1, 3] (Skips 2 due to continue, stops before 4 due to break) ``` ### Applying Loops to Reset the Mapping Now, let's return to our `buy_me_a_coffee.vy` contract and the `withdraw()` function. To reset the `funder_to_amount_funded` mapping, we need to iterate through all the addresses stored in our `self.funders` array and set their corresponding mapped values to zero. This must be done *before* we reset the `self.funders` array itself. We use the "iterate over list elements" style of loop: ```vyper # Inside the withdraw() function, before resetting self.funders # Iterate through each address stored in the self.funders dynamic array for funder: address in self.funders: # Access the mapping using the funder's address as the key # and set the associated amount funded back to 0. # This is a storage write operation. self.funder_to_amount_funded[funder] = 0 # NOW it's safe to reset the array, as we've used its contents self.funders = [] ``` This loop effectively clears the funding amounts associated with all previously recorded funders in the mapping, achieving the desired state reset. ### Critical Consideration: Gas Costs and Unbounded Loops While `for` loops are powerful, they come with significant gas implications on the blockchain: 1. **Iteration Cost:** Each iteration of a loop consumes gas. 2. **Operation Cost:** Operations inside the loop, especially **storage writes** (like setting `self.funder_to_amount_funded[funder] = 0`), are particularly expensive in terms of gas. The total gas cost of our mapping reset loop scales linearly with the number of funders stored in the `self.funders` array. If this array grows very large, the gas required to execute the entire loop within the `withdraw()` function could exceed the block gas limit. **Unbounded Loops Warning:** This scenario highlights the danger of **unbounded loops** – loops that iterate over data structures that can grow indefinitely. If a contract relies on looping through a list of all users, and the number of users becomes too large, the function containing the loop might become unusable because no transaction will have enough gas to complete it. This is a major security and design flaw to avoid. Always consider the potential size of the data you are iterating over and whether there's a risk of exceeding gas limits. Design patterns that avoid iterating over potentially unbounded user data are often necessary. In summary, `for` loops are essential control structures in Vyper, enabling tasks like resetting mappings that cannot be cleared directly. Understanding the different loop syntaxes, the use of `continue` and `break`, and crucially, the significant gas costs associated with iteration and storage writes within loops, is vital for writing safe, efficient, and functional smart contracts. Always be mindful of potential gas limitations when designing loops that operate on dynamic data structures.
When building smart contracts, managing state effectively is crucial. This includes not only storing and updating data but also resetting it when necessary, such as clearing records after a process is complete. In Vyper, different data structures offer varying levels of ease for these operations, particularly when comparing dynamic arrays (DynArray
) and mappings (HashMap
). This lesson explores how for
loops become essential tools for handling these scenarios, focusing on the common challenge of resetting mappings.
Let's consider a scenario like our buy_me_a_coffee.vy
contract, where we track who has funded the contract and how much they've contributed. We might use two state variables:
funders: DynArray[address, N]
- A dynamic array storing the addresses of everyone who has sent funds.
funder_to_amount_funded: HashMap[address, uint256]
- A mapping linking each funder's address to the total amount they've funded.
When a user funds the contract, we update both:
Now, imagine a withdraw()
function where the contract owner retrieves the funds. After withdrawal, we likely want to reset the state for the next funding cycle.
Resetting the DynArray
is straightforward and gas-efficient:
However, resetting the HashMap
is more complex. Unlike arrays, you cannot simply assign an empty structure to a storage mapping in Vyper:
To clear the recorded amounts in the mapping, we must individually update each relevant key-value pair back to its default state (e.g., setting the uint256
amount to 0). This requires iterating through the funders whose balances we need to reset. This is where for
loops become indispensable.
This highlights a key design tradeoff:
Arrays (DynArray
): Easy and cheap to reset entirely. Retrieving a specific value or checking if an element exists often requires iteration (potentially expensive).
Mappings (HashMap
): Excellent for direct lookups (O(1) complexity). Difficult and potentially very gas-expensive to reset, as it requires iterating through keys and performing individual storage writes for each reset.
for
loops provide the mechanism to execute a block of code repeatedly, making them perfect for iterating over collections or ranges. Vyper supports several ways to structure for
loops.
1. Iterating over a Range (End Value)
The range(stop)
function iterates from 0 up to (but not including) the stop
value. Note the mandatory type annotation for the loop variable (i: uint256
).
2. Iterating over a Range (Start and End Values)
You can specify both a starting and ending point using range(start, stop)
. The loop includes start
but excludes stop
.
3. Iterating Directly Over List Elements
Vyper allows iterating directly over the elements of an array (fixed-size or dynamic). The loop variable takes on the value of each element in sequence, not its index.
4. Controlling Loop Flow: continue
and break
Like many programming languages, Vyper provides keywords to control loop execution:
continue
: Skips the rest of the code in the current iteration and proceeds to the next one.
break
: Exits the loop entirely.
Now, let's return to our buy_me_a_coffee.vy
contract and the withdraw()
function. To reset the funder_to_amount_funded
mapping, we need to iterate through all the addresses stored in our self.funders
array and set their corresponding mapped values to zero. This must be done before we reset the self.funders
array itself.
We use the "iterate over list elements" style of loop:
This loop effectively clears the funding amounts associated with all previously recorded funders in the mapping, achieving the desired state reset.
While for
loops are powerful, they come with significant gas implications on the blockchain:
Iteration Cost: Each iteration of a loop consumes gas.
Operation Cost: Operations inside the loop, especially storage writes (like setting self.funder_to_amount_funded[funder] = 0
), are particularly expensive in terms of gas.
The total gas cost of our mapping reset loop scales linearly with the number of funders stored in the self.funders
array. If this array grows very large, the gas required to execute the entire loop within the withdraw()
function could exceed the block gas limit.
Unbounded Loops Warning: This scenario highlights the danger of unbounded loops – loops that iterate over data structures that can grow indefinitely. If a contract relies on looping through a list of all users, and the number of users becomes too large, the function containing the loop might become unusable because no transaction will have enough gas to complete it. This is a major security and design flaw to avoid. Always consider the potential size of the data you are iterating over and whether there's a risk of exceeding gas limits. Design patterns that avoid iterating over potentially unbounded user data are often necessary.
In summary, for
loops are essential control structures in Vyper, enabling tasks like resetting mappings that cannot be cleared directly. Understanding the different loop syntaxes, the use of continue
and break
, and crucially, the significant gas costs associated with iteration and storage writes within loops, is vital for writing safe, efficient, and functional smart contracts. Always be mindful of potential gas limitations when designing loops that operate on dynamic data structures.
A crucial examination of Mastering For Loops in Vyper: Iteration and State Resets - Understand why `for` loops are vital for resetting Vyper mappings, learn the syntax variations, and grasp the significant gas cost differences compared to array resets.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
The basics of blockchain transactions, how to send and receive money on a blockchain network.
How to write Python based smart contracts using Vyper.
How to read and understand Vyper smart contracts.
Vyper data structures, arrays, structs, hash maps.
How to build a smart contract application and deploy on ZKsync with Moccasin.
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
On-chain Data Analyst
$59,000 - $139,000 (avg. salary)
DeFi Developer
$75,000 - $200,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Last updated on June 11, 2025
Duration: 2h 08min
Duration: 2h 32min
Duration: 24min
Course Overview
About the course
The basics of blockchain transactions, how to send and receive money on a blockchain network.
How to write Python based smart contracts using Vyper.
How to read and understand Vyper smart contracts.
Vyper data structures, arrays, structs, hash maps.
How to build a smart contract application and deploy on ZKsync with Moccasin.
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
On-chain Data Analyst
$59,000 - $139,000 (avg. salary)
DeFi Developer
$75,000 - $200,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Last updated on June 11, 2025