What is Gas Optimization?
Gas optimization is the practice of writing smart contract code that minimizes the amount of computational resources required to execute transactions on the Ethereum Virtual Machine (EVM) and other EVM-compatible chains. On Ethereum, every operation — from simple arithmetic to storage reads and writes — consumes a specific amount of “gas,” which users pay for in ETH. Gas optimization directly reduces transaction costs for users, making protocols more accessible and competitive.
Understanding gas optimization is essential for Solidity developers because gas costs can vary dramatically based on coding patterns. Two contracts that accomplish the same logical result can have vastly different gas costs depending on how they’re written. A well-optimized contract might cost users 50% less in gas than a naively written equivalent, which can mean the difference between a protocol that sees widespread adoption and one that users avoid due to prohibitive costs.
The EVM Gas Model
To optimize effectively, developers must understand how the EVM charges for computation:
Opcodes: The EVM has approximately 140 unique opcodes (operations), each with a fixed gas cost. Basic operations like addition (ADD, 3 gas) and multiplication (MUL, 5 gas) are cheap, while storage operations are expensive.
Storage operations: Reading from storage (SLOAD) costs 2,100 gas (warm) or 2,100 gas (cold). Writing to storage (SSTORE) costs 20,000 gas for a new value, 5,000 gas for an update to the same value, and refunds 4,800 gas (with a cap) for clearing a value. Storage is by far the most expensive operation on the EVM.
Memory operations: Memory is a linear, byte-addressable space that expands as needed. Memory expansion costs 3 gas per word (32 bytes), and reading/writing to memory costs 3 gas per word. Memory is significantly cheaper than storage.
Calldata: Input data provided with a transaction. Reading from calldata costs 4 gas per zero byte and 16 gas per non-zero byte. Calldata is read-only and cheaper than memory for pass-through data.
Transaction overhead: Every transaction has a base cost of 21,000 gas, which covers the signature verification and basic processing.
Storage Optimization Techniques
Storage optimization is the single most impactful area for gas reduction, given that storage writes cost 20,000 gas each.
Storage Packing
The EVM stores data in 32-byte (256-bit) slots. A single storage slot can hold multiple variables as long as their combined size does not exceed 32 bytes. Solidity automatically packs storage variables, but only if they are declared together in the contract without gaps.
// BAD: Each variable occupies a full 32-byte slot (3 slots total)
struct User {
uint128 balance; // Slot 0 (16 bytes used, 16 wasted)
bool isActive; // Slot 1 (1 byte used, 31 wasted)
uint64 lastUpdate; // Slot 2 (8 bytes used, 24 wasted)
}
// Cost: 3 storage slots × 20,000 gas = 60,000 gas
// GOOD: All variables fit in one 32-byte slot (1 slot total)
struct User {
uint128 balance; // 16 bytes
uint64 lastUpdate; // 8 bytes
bool isActive; // 1 byte
// Total: 25 bytes, fits in one 32-byte slot
}
// Cost: 1 storage slot × 20,000 gas = 20,000 gas (67% savings)
Developers should carefully order struct members and state variables to maximize packing. Variables are packed in order of their declaration, and items between constant or immutable variables break the packing chain.
Using Memory Instead of Storage
Reading from memory costs 3 gas per word compared to 2,100 gas per SLOAD. Writing to memory costs 3 gas per word compared to 20,000 gas per SSTORE. When possible, cache storage values in memory variables before performing multiple operations:
// BAD: Reads from storage in every iteration
for (uint i = 0; i < users.length; i++) {
if (users[i].balance > threshold) { ... }
}
// GOOD: Cache storage array length in memory
uint256 len = users.length;
for (uint i = 0; i < len; i++) {
if (users[i].balance > threshold) { ... }
}
Using Calldata Instead of Memory
For read-only function parameters, using calldata instead of memory avoids the cost of copying data from calldata to memory. In external functions, calldata parameters are read directly from the transaction input, saving both gas and execution time.
// Slightly more expensive (copies calldata to memory)
function process(bytes memory data) external { ... }
// Cheaper (reads directly from calldata)
function process(bytes calldata data) external { ... }
Mapping vs. Array
Mappings in Solidity only store non-zero entries, making them gas-efficient for sparse data. Arrays store all entries contiguously, including zero values. For large collections where most entries are zero or unset, mappings can be significantly more gas-efficient.
Computational Optimization
Short-Circuit Evaluation
Use && and || operators to place cheaper checks before expensive ones, leveraging short-circuit evaluation where the second operand is only evaluated if necessary:
// GOOD: Cheap check first
require(isValid && expensiveCheck(), "Invalid");
// BAD: Expensive check evaluated even if isValid is false
require(expensiveCheck() && isValid, "Invalid");
Loop Optimization
Loops are a common source of gas inefficiency. Best practices include:
- Bounding loop iterations with a maximum constant to prevent infinite gas consumption.
- Caching array length in memory before the loop.
- Using
unchecked {}blocks for loop counters when overflow is impossible. - Minimizing storage reads/writes inside loops.
Unchecked Arithmetic
Solidity 0.8+ includes built-in overflow/underflow checks that cost additional gas. When you can mathematically prove that overflow is impossible (e.g., a loop counter that starts at 0 and has a bounded upper limit), wrapping the arithmetic in an unchecked {} block saves gas:
for (uint i = 0; i < maxIterations;) {
// ... loop body ...
unchecked { ++i; }
}
Event Optimization
Emitting events is cheaper than storing data on-chain for information that needs to be accessible off-chain. Events cost approximately 375 gas per topic (up to 3 indexed parameters) plus the data cost for non-indexed parameters. Use events for:
- Audit trails and transaction logs
- Data that needs to be queried off-chain (e.g., by The Graph)
- Informational outputs that don’t affect contract logic
Remember that events cannot be read by other contracts — they are only accessible from outside the blockchain through event logs.
Advanced Techniques
Custom Errors (Solidity 0.8.4+)
Replacing require condition strings with custom errors saves significant gas because error strings are stored in calldata and cost gas per character:
// EXPENSIVE: Error string stored in calldata (16 gas per non-zero byte)
require(balance > 0, "Insufficient balance");
// CHEAPER: Custom error selector (only 4 bytes)
error InsufficientBalance();
require(balance > 0, InsufficientBalance());
For frequently called functions, this optimization can save hundreds or thousands of gas per transaction.
Proxy Pattern Considerations
When using proxy patterns (UUPS, transparent proxy), be aware of the additional gas cost for storage reads that fall through to the implementation contract. Storage slot layout must be carefully managed to avoid conflicts between the proxy and implementation.
Assembly (Yul)
Inline assembly (Yul) can provide fine-grained gas optimizations that are not possible in pure Solidity. Common use cases include efficient memory management, packed arithmetic, and direct storage operations. However, assembly should be used sparingly because it bypasses Solidity’s safety checks and is harder to audit.
Function Selectors
External function calls use a 4-byte function selector. When making external calls to known contracts, caching the function selector as a bytes4 constant saves the gas cost of computing it each time.
Tools for Gas Optimization
Several tools help developers identify gas inefficiencies:
- Slither (by Trail of Bits): Static analysis tool that detects gas optimization opportunities.
- Foundry Gas Reports: The
forge test --gas-reportcommand provides per-function gas usage. - Hardhat Gas Reporter: Plugin that reports gas usage for each test.
- Etherscan Gas Tracker: Provides real-time gas prices and estimated transaction costs.
- Tenderly: Simulation platform that shows detailed gas breakdowns.
Key Considerations
- Storage operations are the most expensive EVM operations — optimize them first.
- Use storage packing to fit multiple variables into single 32-byte slots.
- Cache storage variables in memory for operations that read them multiple times.
- Use calldata instead of memory for read-only external function parameters.
- Custom errors save gas compared to string-based require messages.
- Use unchecked blocks for arithmetic that cannot overflow.
- Always profile gas usage with tools before deploying to mainnet.
Related Terms
- Smart Contract
- EVM
- Proxy Pattern
- Contract Audit
- Event Log