ethereum/gas-optimization

Solidity Gas Optimization Guide

ethereumguide👥 Communityconfidence highhealth -2%
v1.0.0·Updated 3/20/2026

Storage Optimization (Maximum Gains)

Variable Packing

// ❌ Bad: 3 slots (each slot is 32 bytes)
uint256 a;  // slot 0
uint128 b;  // slot 1 (wastes 128 bits)
uint128 c;  // slot 2 (wastes 128 bits)

// ✅ Good: 2 slots
uint256 a;  // slot 0
uint128 b;  // first half of slot 1
uint128 c;  // second half of slot 1 (automatically packed)

Rule: Adjacent small-type variables are packed into the same slot in declaration order.

Use bytes32 Instead of string (Fixed Length)

// ❌ string stores dynamic length, at least 1 slot + content
string public name;

// ✅ bytes32 is fixed at 1 slot, suitable for short strings
bytes32 public name;

Reduce SSTORE/SLOAD (Most Expensive Operations)

OperationGas
SSTORE (cold storage, first write)20,000
SSTORE (warm storage, already written)2,900
SLOAD (cold read)2,100
SLOAD (warm read)100
// ❌ Reading the same storage variable multiple times
function bad() external {
  for (uint i = 0; i < arr.length; i++) {  // arr.length reads from storage each time
    total += arr[i];
  }
}

// ✅ Cache to memory
function good() external {
  uint len = arr.length;  // read once and store in memory
  for (uint i = 0; i < len; i++) {
    total += arr[i];
  }
}

Calldata vs Memory

// ❌ memory copies data (unnecessary memory operations)
function process(uint[] memory data) external { ... }

// ✅ calldata reads directly without copying (use calldata for read-only external function parameters)
function process(uint[] calldata data) external { ... }

unchecked Arithmetic (Solidity 0.8+)

// ❌ checked arithmetic (adds overflow check to each operation, +20 gas)
for (uint i = 0; i < 100; i++) { ... }

// ✅ Use unchecked when overflow is guaranteed not to occur
for (uint i = 0; i < 100;) {
  // ... loop body
  unchecked { i++; }  // saves ~30 gas per iteration
}

Custom Errors vs require string

// ❌ require string is more expensive for both deployment and calls
require(amount > 0, "Amount must be positive");

// ✅ Custom errors (Solidity 0.8.4+) save gas
error InvalidAmount();
if (amount == 0) revert InvalidAmount();

Events Instead of Storage (When Only Historical Records Are Needed)

// ❌ Storing all historical records (extremely expensive)
Transfer[] public history;

// ✅ emit events (375 gas vs 20000 gas)
event Transfer(address indexed from, address indexed to, uint256 amount);
emit Transfer(from, to, amount);

Function Visibility Optimization

// public generates two entry points (internal + external), external saves gas
// ✅ Use external when only called externally
function transfer(address to, uint256 amount) external { ... }

Constants and Immutables

// ✅ constant: replaced at compile time, 0 storage gas
uint256 public constant MAX_SUPPLY = 10000;

// ✅ immutable: written to bytecode at deployment, reading costs only 3 gas
address public immutable owner;
constructor() { owner = msg.sender; }

Bitmap Instead of bool Array

// ❌ bool[] each bool occupies 1 slot (20000 gas each)
mapping(uint => bool) public claimed;

// ✅ bitmap: each uint256 stores 256 bools
mapping(uint => uint256) private _claimedBitmap;
function isClaimed(uint tokenId) public view returns (bool) {
  return _claimedBitmap[tokenId / 256] & (1 << (tokenId % 256)) != 0;
}

Gas Analysis Tools

  • forge snapshot — Foundry gas snapshot comparison
  • hardhat-gas-reporter — Hardhat test gas reports
  • Tenderly Debugger — Per-operation gas analysis
  • ETH Gas Station — Real-time gas price reference