🎓 A1Learner
🔍
0 of 48 lessons completed

Solidity Loop Gas Patterns

Loop gas patterns are habits that keep loops cheap and bounded. A loop that grows without a bound can run out of gas.

A bounded loop sums a fixed array

The loop runs over four stored values and then stops. Its sum is fixed and easy to check.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract LoopSum {
    uint256[] public nums;

    constructor() {
        nums.push(1);
        nums.push(2);
        nums.push(3);
        nums.push(4);
    }

    function sum() public view returns (uint256) {
        uint256 total = 0;
        for (uint256 i = 0; i < nums.length; i++) {
            total += nums[i];
        }
        return total;
    }
}

A cached length gives the same sum

Reading the length once gives the same answer as reading it on every pass. The cached version avoids the repeat reads.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract CachedLength {
    uint256[] public nums;

    constructor() {
        nums.push(1);
        nums.push(2);
        nums.push(3);
        nums.push(4);
    }

    function sumDirect() public view returns (uint256) {
        uint256 total = 0;
        for (uint256 i = 0; i < nums.length; i++) {
            total += nums[i];
        }
        return total;
    }

    function sumCached() public view returns (uint256) {
        uint256 len = nums.length;
        uint256 total = 0;
        for (uint256 i = 0; i < len; i++) {
            total += nums[i];
        }
        return total;
    }
}

A loop can stop as soon as it finds its answer

Returning early skips the passes after the answer is found. A search with no match reports the full length instead.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract EarlyExit {
    uint256[] public nums;

    constructor() {
        nums.push(1);
        nums.push(2);
        nums.push(3);
        nums.push(4);
    }

    function firstAbove(uint256 threshold) public view returns (uint256) {
        for (uint256 i = 0; i < nums.length; i++) {
            if (nums[i] > threshold) {
                return i;
            }
        }
        return nums.length;
    }
}

Example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract BatchTotals {
    uint256[] public nums;

    constructor() {
        nums.push(1);
        nums.push(2);
        nums.push(3);
        nums.push(4);
    }

    function sumAll() public view returns (uint256) {
        uint256 len = nums.length;
        uint256 total = 0;
        for (uint256 i = 0; i < len; i++) {
            total += nums[i];
        }
        return total;
    }

    function sumUpTo(uint256 n) public view returns (uint256) {
        uint256 len = nums.length;
        if (n > len) {
            n = len;
        }
        uint256 total = 0;
        for (uint256 i = 0; i < n; i++) {
            total += nums[i];
        }
        return total;
    }

    function countAbove(uint256 threshold) public view returns (uint256) {
        uint256 len = nums.length;
        uint256 count = 0;
        for (uint256 i = 0; i < len; i++) {
            if (nums[i] > threshold) {
                count += 1;
            }
        }
        return count;
    }
}

Key points

  • Bounded loop: a loop over a fixed set of items always finishes.
  • Storage reads: reading state inside a loop repeats work on every pass.
  • Cached length: reading the length once and reusing it avoids repeat reads.
  • Early exit: stopping as soon as the answer is found skips the rest of the loop.
  • Unbounded loop: a loop over a growing array may never finish within the gas limit.

In this example, one contract sums a fixed list, sums only its first items, and counts how many items pass a threshold. Each function uses a bounded loop over the same stored list.

Note

gas cost grows with each pass, so keep loops short and bounded.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Remix IDE

Exercise

What makes a loop risky for gas?