Tutoriel·7 min de lecture·Par Solingo

How to Write Gas-Efficient Loops in Solidity

Learn practical techniques to optimize loops and reduce gas costs in your smart contracts.

# How to Write Gas-Efficient Loops in Solidity

Loops are gas-intensive operations in Solidity. Every iteration costs gas, and inefficient loop patterns can drain users' wallets. This guide covers proven optimization techniques.

The Problem with Naive Loops

Consider this common pattern:

contract BadLoop {

address[] public users;

function sumBalances() public view returns (uint256) {

uint256 total = 0;

for (uint256 i = 0; i < users.length; i++) {

total += address(users[i]).balance;

}

return total;

}

}

This code reads users.length from storage on every iteration — extremely wasteful.

Optimization 1: Cache Array Length

function sumBalancesOptimized() public view returns (uint256) {

uint256 total = 0;

uint256 len = users.length; // Cache length

for (uint256 i = 0; i < len; i++) {

total += address(users[i]).balance;

}

return total;

}

Savings: ~2,100 gas per iteration for storage reads.

Optimization 2: Unchecked Arithmetic

function sumBalancesCheaper() public view returns (uint256) {

uint256 total = 0;

uint256 len = users.length;

for (uint256 i = 0; i < len;) {

total += address(users[i]).balance;

unchecked { ++i; }

}

return total;

}

Since i can't overflow in reasonable array sizes, skip overflow checks.

Savings: ~40 gas per iteration.

Optimization 3: Use ++i Instead of i++

// Costs more (creates temporary copy)

for (uint256 i = 0; i < len; i++) { }

// Costs less (increments directly)

for (uint256 i = 0; i < len; ++i) { }

Savings: ~5 gas per iteration.

Optimization 4: Avoid Storage Writes in Loops

// BAD: Writes to storage every iteration

mapping(address => uint256) public scores;

function badUpdate(address[] calldata addrs) external {

for (uint256 i = 0; i < addrs.length; i++) {

scores[addrs[i]]++; // 5,000+ gas per write

}

}

// GOOD: Batch updates when possible

function goodUpdate(address[] calldata addrs, uint256[] calldata amounts) external {

require(addrs.length == amounts.length, "Length mismatch");

for (uint256 i = 0; i < addrs.length;) {

scores[addrs[i]] = amounts[i]; // Single write per user

unchecked { ++i; }

}

}

Optimization 5: Early Exit Conditions

function findUser(address target) public view returns (bool) {

uint256 len = users.length;

for (uint256 i = 0; i < len;) {

if (users[i] == target) return true; // Exit early

unchecked { ++i; }

}

return false;

}

Full Optimized Pattern

contract OptimizedLoop {

address[] public users;

function processUsers() external {

uint256 len = users.length;

address user;

for (uint256 i = 0; i < len;) {

user = users[i];

// Process user...

unchecked { ++i; }

}

}

}

When to Avoid Loops Entirely

If unbounded arrays are involved, consider:

  • Pagination: Process chunks across multiple transactions
  • Pull over Push: Let users claim rewards individually
  • Events + Off-Chain: Emit events, aggregate off-chain

Benchmarks

| Pattern | Gas Cost (100 iterations) |

|---------|---------------------------|

| Naive loop | 450,000 |

| Cached length | 240,000 |

| + unchecked | 236,000 |

| + ++i | 235,500 |

Key Takeaways

  • Always cache .length outside loops
  • Use unchecked { ++i; } for counters
  • Minimize storage reads/writes
  • Consider alternatives to loops for unbounded data
  • Test gas costs with forge snapshot
  • Loops are unavoidable, but smart optimization can save thousands of gas per transaction.

    Prêt à mettre en pratique ?

    Applique ces concepts avec des exercices interactifs sur Solingo.

    Commencer gratuitement