Control flow

Conditionals, loops, and error handling — with a focus on the patterns that actually matter in production.

Conditionals

Solidity supports standard if/else syntax.

solidity
function classify(uint256 amount) public pure returns (string memory) {
    if (amount == 0) {
        return 'zero';
    } else if (amount < 100) {
        return 'small';
    } else {
        return 'large';
    }
}

Loops

Solidity supports for, while, and do...while loops.

solidity
function sumArray(uint256[] memory values) public pure returns (uint256 total) {
    for (uint256 i = 0; i < values.length; i++) {
        total += values[i];
    }
}

Be careful with loops in production contracts. Each iteration costs gas. If an attacker can make your array unboundedly large, they can cause your function to run out of gas and revert — a denial-of-service attack. Prefer mappings over arrays for large data sets.

require, revert, and assert

These are Solidity's primary error-handling tools. Getting them right is critical for both security and UX.

require

The workhorse of input validation. Reverts the transaction with an error message if the condition is false. Any gas spent up to that point is consumed; unused gas is refunded.

solidity
function withdraw(uint256 amount) public {
    require(amount > 0, 'Amount must be positive');
    require(balances[msg.sender] >= amount, 'Insufficient balance');
    balances[msg.sender] -= amount;
    payable(msg.sender).transfer(amount);
}

revert

Explicitly reverts with a custom error. More flexible than require — useful for complex conditionals or when you want to use custom error types (more gas-efficient than string messages).

solidity
error InsufficientBalance(uint256 requested, uint256 available);

function withdraw(uint256 amount) public {
    if (balances[msg.sender] < amount) {
        revert InsufficientBalance(amount, balances[msg.sender]);
    }
    balances[msg.sender] -= amount;
    payable(msg.sender).transfer(amount);
}

assert

Used to check for internal invariants — conditions that should never be false if the contract logic is correct. Unlike require, a failed assert consumes all remaining gas. Use it sparingly.

solidity
function split(uint256 total, uint256 a, uint256 b) internal pure {
    assert(a + b == total);  // This should always hold — if not, something is very wrong
}

try/catch

When calling external contracts, use try/catch to handle failures gracefully without reverting your whole transaction.

solidity
interface IExternalContract {
    function riskyCall() external returns (bool);
}

function safeCall(address target) public returns (bool success) {
    try IExternalContract(target).riskyCall() returns (bool result) {
        return result;
    } catch {
        // External call failed — handle gracefully
        return false;
    }
}
←   Functions and visibilityMappings and arrays   →