Solia saysSolidity packs adjacent state variables into the same 32-byte storage slot when they fit, turning two writes into one and halving the cold-init gas.
Storage costs 22,100 gas to initialize a slot (20,000 init + 2,100 cold access). The biggest optimization is variable packing: declare adjacent state variables that fit in 32 bytes (two uint128, four uint64, etc.) and Solidity packs them into one slot — paying the cold-init cost once instead of per variable.
Packing is positional: variables are packed in declaration order, and a new variable starts a new slot if it does not fit in what is left. Mappings and dynamic arrays always start a fresh slot — their data lives at keccak256(slot ‖ key) for mappings and keccak256(slot) + index for arrays. The demo compares packed vs unpacked init costs.
Power-ups you unlock
Adjacent variables sharing a 32-byte slot pack automatically
Two uint128 fit in one slot; four uint64 too
Packing halves (or quarters) the cold-init cost
Mappings live at keccak(slot ‖ key)
Dynamic arrays live at keccak(slot) + index
The Reentrancy Reaper attacks — common mistakes
Reordering variables and breaking packing accidentally
Assuming a struct in a mapping packs the way source order suggests
Forgetting reads of packed slots also share the load (cheaper)
Trying to pack a mapping into another slot (mappings always start fresh)
Boss battleCompare gas to initialize two separate uint256 fields versus two uint128 fields packed into one slot.
Example code
<!doctype html><html><head><meta charset="utf-8"></head>
<body style="background:#06040d;color:#e6e0ff;font-family:monospace;padding:20px"><pre id="o"></pre>
<script>
const SSTORE_COLD_INIT = 22100; // 20000 init + 2100 cold access
// unpacked: two uint256 → two slots, two cold inits
const unpacked = 2 * SSTORE_COLD_INIT;
// packed: two uint128 → one slot, one cold init
const packed = SSTORE_COLD_INIT;
const savings = unpacked - packed;
document.getElementById('o').textContent = [
'gas to initialize two adjacent state vars:',
' uint256 a; uint256 b; → ' + unpacked.toLocaleString() + ' gas (two slots)',
' uint128 a; uint128 b; → ' + packed.toLocaleString() + ' gas (one slot, packed)',
' savings: ' + savings.toLocaleString() + ' gas (' + Math.round(100*savings/unpacked) + '% off)',
'',
'slot-index rules:',
' mapping(K=>V) v; → data at keccak256(key ‖ slot)',
' T[] arr; → data at keccak256(slot) + index',
' mappings & dynamic arrays always start a fresh slot'
].join('\n');
</script></body></html>