storage slot
A storage slot is one addressable cell in a contract's persistent storage: a 32-byte (256-bit) box identified by a 256-bit key. The contract's storage is just a map from these keys to these 32-byte values, and every variable a contract keeps ultimately lives in one or more slots. Understanding how high-level variables map onto slots is the difference between reading a contract's raw storage and being baffled by it.
Solidity assigns slots by a deterministic layout. Fixed-size state variables are placed sequentially starting at slot 0 in declaration order. To save space, multiple variables smaller than 32 bytes are packed into the same slot when they fit — for instance a uint128, a uint64, and a uint64 can share one slot, so updating all three together costs a single SSTORE. This packing is the most reliable gas optimization available, but it only helps if the variables are written together.
Dynamic and keyed data cannot sit at fixed offsets because their size is unknown at compile time, so Solidity derives their slots by hashing. For a mapping declared at slot p, the value for key k lives at keccak256(h(k) . p) (the key padded and concatenated with the slot number, then hashed). For a dynamic array at slot p, the length is stored in slot p and the elements start at keccak256(p), laid out consecutively. Nested mappings hash iteratively. This scattering by hash is also why you cannot enumerate a mapping's keys on-chain — the slots are spread pseudo-randomly across the 2²⁵⁶ space.
Because layout is positional, it becomes a hard constraint for upgradeable contracts: a new implementation reached via delegatecall must keep the exact same slot ordering, or it will read and write the proxy's existing data through the wrong variables. Storage-collision bugs of this kind have caused real exploits, which is why patterns like ERC-7201 'namespaced storage' deliberately place structs at hashed, collision-resistant base slots.
Mapping values are scattered across storage by hashing the key with the slot number.