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)
| Operation | Gas |
|---|---|
| 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 comparisonhardhat-gas-reporter— Hardhat test gas reports- Tenderly Debugger — Per-operation gas analysis
- ETH Gas Station — Real-time gas price reference