What Gas Is and Why It Matters
Every operation on the Ethereum Virtual Machine consumes gas, a unit measuring computational effort, and each opcode has a fixed gas price defined in the protocol. The transaction sender pays gas_used multiplied by the gas price in ETH, so inefficient code directly costs users money and can even exceed the block gas limit, making a function uncallable. Optimizing gas means understanding which operations are expensive: storage writes and reads dominate, followed by contract calls and memory expansion, while arithmetic and stack operations are comparatively cheap. Effective optimization targets the costly operations, not the trivial ones.
Cricket analogy: Every ball bowled draws from the over quota, and each EVM opcode draws from the gas budget the way a team's allotted overs cap how much they can do.
Storage Is the Big Cost
Storage operations dwarf everything else: a cold SLOAD costs 2,100 gas and an SSTORE writing a non-zero value to a fresh slot costs 20,000 gas, while a memory or stack operation costs a handful. The most impactful optimization is therefore minimizing storage access and packing variables. The EVM stores data in 32-byte slots, and multiple smaller variables declared consecutively can share one slot: for example a uint128, a uint64, and a uint64 pack into a single 32-byte slot, turning three storage writes into one. Ordering struct and state-variable declarations to fill slots tightly is one of the highest-leverage gas wins available.
Cricket analogy: Storage is like updating the permanent record books while memory is scribbling on a dugout notepad, and an SSTORE to the ledger is the priciest operation of all.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract GasExamples {
// Struct packing: three fields fit in ONE 32-byte storage slot
struct Packed {
uint128 a; // 16 bytes
uint64 b; // 8 bytes
uint64 c; // 8 bytes -> 32 bytes total, single slot
}
uint256[] private data;
// Cache storage length and use unchecked for the loop counter
function sum() external view returns (uint256 total) {
uint256 len = data.length; // one SLOAD, then reuse from memory
for (uint256 i; i < len; ) {
total += data[i];
unchecked { ++i; } // safe: i < len can never overflow
}
}
// calldata avoids copying the array into memory
function process(uint256[] calldata items) external pure returns (uint256 n) {
n = items.length;
}
}High-Leverage Optimization Patterns
Beyond packing, several patterns cut gas reliably. Cache repeated storage reads in a memory (local) variable so you pay one SLOAD instead of many. Use calldata instead of memory for external function array and struct parameters to avoid a copy. Mark values that never change as constant (compile-time) or immutable (set once in the constructor); both avoid storage slots entirely and are read from the bytecode. Use unchecked blocks for arithmetic you have proven cannot overflow, such as loop increments, to skip the default 0.8 overflow checks. Prefer custom errors over revert strings, and short-circuit cheap conditions first in && and || expressions.
Cricket analogy: Fetching the drinks-break stats once and reusing them instead of radioing the scorers every ball is like caching a storage variable in memory to avoid repeated SLOADs.
constant and immutable both eliminate a storage slot, but differ in when the value is fixed: constant must be known at compile time and is inlined into the bytecode, while immutable is assigned once in the constructor. Use immutable for deploy-time values like a token address, constant for fixed literals.
Trade-offs and Pitfalls
Gas optimization has diminishing returns and real risks. Aggressive gas golfing can obscure logic, remove safety checks, and introduce bugs, so favor clarity unless profiling shows a hotspot worth optimizing. Two pitfalls deserve special attention: unbounded loops that iterate over user-growable arrays can eventually exceed the block gas limit and permanently brick a function, which is also a denial-of-service vector; and micro-optimizing rarely-called functions wastes effort better spent on the hot path. Measure first with a gas profiler or forge test --gas-report, optimize the operations that dominate real usage, and never trade away a security invariant for a few thousand gas.
Cricket analogy: Chasing every single risks a run-out, so over-optimizing gas until code is unreadable and buggy is like abandoning sound technique for reckless quick runs.
Never sacrifice a security check to save gas. Removing a require, skipping a reentrancy guard, or using unchecked math on unvalidated input to shave gas is a classic path to an exploit. Optimize the mechanics (storage layout, caching, calldata) not the safety invariants.
- Gas measures EVM computational cost; users pay gas_used times gas price, and exceeding the block gas limit bricks a function.
- Storage dominates: a cold SLOAD is ~2,100 gas and a fresh non-zero SSTORE is ~20,000, versus a few for stack/memory ops.
- Pack multiple small variables into shared 32-byte slots by ordering declarations tightly.
- Cache repeated storage reads in memory, and use calldata over memory for external parameters.
- constant and immutable avoid storage slots; use unchecked for provably-safe arithmetic like loop counters.
- Prefer custom errors over revert strings and short-circuit cheap conditions first.
- Avoid unbounded loops (a DoS risk), profile before optimizing, and never trade away security for gas.
Practice what you learned
1. Which category of operation is the most expensive in the EVM?
2. What is the benefit of variable packing?
3. When is an unchecked block appropriate?
4. Why prefer calldata over memory for external function array parameters?
5. What is a key danger of an unbounded loop over a user-growable array?
Was this page helpful?
You May Also Like
Reentrancy and Common Vulnerabilities
How reentrancy attacks drain contracts by re-entering functions before state updates, plus the defensive patterns and other common Solidity vulnerability classes.
ERC-721 NFT Standard
The standard for non-fungible tokens on Ethereum, covering unique token IDs, ownership and metadata, safe transfers with receiver hooks, and minting.
ERC-20 Token Standard
The interface and mechanics behind fungible tokens on Ethereum, covering core functions, the approve/allowance pattern, events, and safe implementation.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics