Intro to Control Flow in Solidity

Introducing the basic control flow structures, like loops and conditionals, in Solidity.

July 22, 2022 | 5 minute read

In programming, control flow refers to the flow of execution of your code. Practically speaking, this means conditionals and loops.

Solidity has a set of control structures, just like other programming languages. And this article covers what they are and how to use them. Specifically, this article covers conditionals (if and else) and loops (for, while, and do...while).

Conditionals

If you’re coming from JavaScript, Go, Java, or any other C-like language, then Solidity’s version of conditionals will look very familiar. Solidity uses if and else in combination to test any set of conditions.

Here’s a basic example:

contract FavoriteNumber {
	uint favoriteNumber;
	
	constructor(uint _number) {
		favoriteNumber = _number;	
	}

	function checkFavoriteNumber() public view returns (uint) {
		if (favoriteNumber < 50) {
			// do something
		} else if (favoriteNumber < 100) {
			// do something else		
		} else {
			// do something else
		}

		return favoriteNumber;
	}
}

Just like in JavaScript, if and else can be combined to test any set of conditions.

Loops

Solidity provides a couple of structures for looping. Namely, for, while, and do...while, which I’ll explain below.

Before discussing specific loops, it’s important to remember that each additional line of code adds to the cost of running a function. Because of this, you won’t see many loops in production smart contracts. Additionally, you should use loop control keywords like break, continue, and return to avoid unnecessary execution. Every line of code counts!

for loops

for loops in Solidity look and act just like they do in JavaScript:

uint counter = 10;
event Number(uint _number);

for (uint i = 0; i < counter; i++) {
emit Number( i );
}

Here we have a counter that we will count up to. Our loop declares an increment variable (uint i = 0;), then our breaking condition (i < counter;), and finally our incrementor (i++). With each iteration of the loop, we emit an event, called Number.

That is a trivial example of a loop. We can look at a more complex example of one in the wild:

// Payout each royalty
for (uint256 i = 0; i < numRecipients; ) {

	// Cache the recipient and amount
	recipient = recipients[i];
	amount = amounts[i];

	// Ensure that we aren't somehow paying out more than we have
	require(amountRemaining >= amount, "insolvent");

	// Transfer to the recipient
	_handleOutgoingTransfer(recipient, amount, _payoutCurrency, 50000);
	emit RoyaltyPayout(_tokenContract, _tokenId, recipient, amount);
	
	// Cannot underflow as remaining amount is ensured to be greater than or equal to royalty amount
	unchecked {
		amountRemaining -= amount;
		++i;
	}
}

This code is taken from Zora v3 FeePayoutSupport contract, which is used for paying out protocol fees and royalties. This loop goes through two lists, one of royalty recipients and one of amounts. For each recipient, amount combination, it transfers that amount owed to the recipient.

while loops

Just like in other languages, we have to be careful with while loops to ensure we create one that actually ends. You’ll rarely see while loops in production Solidity code.

A while loop in Solidity looks like this:

uint counter = 10;
event Number(uint _number);

while (i < counter) {
	emit Number(i);
	i = i + 1;
}

We have a counter (counter) and an incrementor (i). With each iteration of the loop, we emit an event for the value of i and then increment i by 1. Once i reaches 10, the loop will stop.

Here’s an example of such a while loop in a production contract:

uint256 temp = value;
uint256 digits;

while (temp != 0) {
	digits++;
	temp /= 10;
}

This loop is from OpenZeppelin’s Strings.sol utility contract, which has a method for turning an uint256 into a string.

do...while loops

The last looping structure available in Solidity is the do...while loop. This is similar to a while loop, but the structure is inverted, so the code block runs at least once. The basic syntax looks like this:

uint counter = 10;
uint i = 0;

do  {
	emit uintNumber( blockNumber[i] );
	i = i + 1; 
} while (i < counter);

The above block emits at least one event for the first iteration of the loop. That’s because the do block executes before the condition is checked for the first time.

Here’s an example of this in the wild:

function _mintNFTs(address to, uint256 quantity) internal {
    do {
        uint256 toMint = quantity > MAX_MINT_BATCH_SIZE
            ? MAX_MINT_BATCH_SIZE
            : quantity;
        _mint({to: to, quantity: toMint});
        quantity -= toMint;
    } while (quantity > 0);
}

This function is from the Zora Drops contract, which powers http://create.zora.co. It’s looping down from the quantity provided by the caller and minting some number of NFTs to the to address.

Conclusion

This article has covered the syntax for basic control flow in Solidity. The language was inspired by Java, Go, and JavaScript, so these constructs should look familiar to a programmer coming from these languages.

if and else can be combed to check a complex set of conditions. Solidity does also provide the ternary operator-style condition checking for tersely checking a single condition.

Solidity’s looping structures are for, while, and do...while. All three are powerful, but should be used intentionally and sparingly, since each line of code increases the transaction fee for running a function. You should always avoid running a loop over a list that doesn’t have a fixed size.