โ†
Previous Module
Transaction Debugging

๐Ÿ’พ Storage Patterns: Packing & Slots

Master storage layout optimization to reduce SSTORE costs

Write efficient smart contracts that save money

๐Ÿ’พ Storage Optimization Patterns

Storage is the most expensive resource in Ethereum. A single SSTORE costs 20,000 gas (vs 3 for memory). But the EVM stores data in 32-byte slotsโ€”if you use uint256 for everything, you waste space. Storage packing fits multiple small variables (uint128, uint64, uint32) into one slot, reducing writes by 66-90%. Choosing between mappings vs arrays matters: mappings are O(1) but can't iterate; arrays iterate but cost gas per element. Memory cachingโ€”reading storage once into memory, then operating on memoryโ€”cuts repeated SLOAD costs. Production contracts (Uniswap, Aave) obsess over storage layout. One optimization can save millions in aggregate gas fees.

๐ŸŽฎ Interactive: Storage Pattern Comparison

Select a storage pattern to see gas costs, code examples, and when to use each approach.

๐Ÿ“ฆ

Unpacked Storage

Each variable occupies full 32-byte slot (wasteful)

Write Cost
60,000
gas per write
Read Cost
6,300
gas per read
Storage Slots
3
32-byte slots
๐Ÿ’ป Code Example
// โŒ BAD: 3 storage slots
uint256 a;  // slot 0
uint256 b;  // slot 1
uint256 c;  // slot 2

// Cost: 3 ร— 20,000 = 60,000 gas
๐ŸŽฏ Best Use Case
None - default Solidity behavior

๐ŸŽ Storage Packing Rules

1. Declare small types together: Group uint128, uint64, uint32, address in sequence. Solidity packs them into one slot automatically.
2. Order by size (largest first): Put uint256 and bytes32 first, then smaller types. Prevents slot fragmentation.
3. Use structs for related data: Struct members pack together. struct User { uint128 balance; uint64 timestamp; address addr; } = 1 slot!
4. Avoid mixing structs and primitives: Breaking up structs with standalone variables disrupts packing alignment.

๐Ÿ“Š Mapping vs Array Decision Tree

Use Mapping when:
  • โ€ข Sparse data (not every key has a value)
  • โ€ข Need O(1) lookups by key (address, uint, bytes32)
  • โ€ข Don't need to iterate over all entries
  • โ€ข Example: mapping(address => uint) for balances
Use Array when:
  • โ€ข Dense data (most indices filled)
  • โ€ข Need to iterate over all elements
  • โ€ข Small, bounded size (<100 elements)
  • โ€ข Example: address[] owners for multisig
โš ๏ธ Never Do:
  • โ€ข Unbounded array iteration (DoS attack vector)
  • โ€ข Delete array elements without shifting (leaves gaps)
  • โ€ข Store large objects in arrays (high gas per push)

๐Ÿง  Memory Caching Pattern

โŒ BAD: Repeated SLOAD
function badLoop() external {
  for (uint i = 0; i < 10; i++) {
    total += values[i];
    // SLOAD: 10 ร— 2,100 = 21,000 gas
  }
}
โœ… GOOD: Cache in Memory
function goodLoop() external {
  uint _total = total; // 1 SLOAD
  for (uint i = 0; i < 10; i++) {
    _total += values[i];
  }
  total = _total; // 1 SSTORE
  // Savings: 18,900 gas (90%!)
}