Solidity Storage Packing
Storage packing is the way small state variables share one 32-byte storage slot. Packing keeps related small values together instead of giving each one a whole slot.
Two small values share one slot
Two uint128 values fit side by side in one slot. The contract below stores both and reads both back.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract PairPack {
uint128 public first;
uint128 public second;
function set(uint128 a, uint128 b) public {
first = a;
second = b;
}
}Three small values share one slot
A uint128 plus two uint64 values also fit together. All three read back unchanged.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract TrioPack {
uint128 public big;
uint64 public mid;
uint64 public small;
function set(uint128 a, uint64 b, uint64 c) public {
big = a;
mid = b;
small = c;
}
}Small values pack around a large value
Small values group together even when a large value sits nearby. Each value still reads back correctly.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract MixedPack {
bool public active;
uint8 public level;
uint16 public score;
uint256 public total;
function set(bool a, uint8 l, uint16 s, uint256 t) public {
active = a;
level = l;
score = s;
total = t;
}
}Example
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract GameProfile {
uint128 public coins;
uint128 public gems;
uint64 public level;
uint64 public wins;
bool public premium;
function setProfile(uint128 c, uint128 g, uint64 l, uint64 w, bool p) public {
coins = c;
gems = g;
level = l;
wins = w;
premium = p;
}
function addWin() public {
wins += 1;
}
}Key points
- Storage slot: one 32-byte home for state values.
- Packing: small values declared next to each other can share one slot.
- Declaration order: neighbours pack together, so group small values side by side.
- Large values: a single uint256 still takes a whole slot on its own.
- Reading back: packed values read back exactly as they were written.
In this example, a game profile stores coins and gems as uint128 values and level and wins as uint64 values with a premium flag beside them. Setting the profile and adding one win leaves every value reading back correctly.
Note
slot layout is a compiler detail, so prove packing by values read back and never by slot numbers.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Remix IDE
Exercise
Which declaration order packs best?