Smart-contract development

mapping

A mapping is Solidity's hash-table: it links keys to values, like a dictionary that turns an address into a balance. The twist is that you never 'add' a key. Conceptually every possible key already maps to the type's zero value, and writing to a key simply overwrites that default. So a freshly deployed mapping(address => uint256) already 'contains' a balance of 0 for all 2^160 addresses; you are only ever changing some of them.

Under the hood there is no table at all. For a mapping declared at storage slot p, the value for key k lives at storage position keccak256(h(k) . p), where h(k) is the key padded to 32 bytes and concatenated with the 32-byte slot number before hashing. Because each entry is scattered to a hash-derived slot, and the EVM cannot invert keccak-256, there is no list of which keys were ever used. That is exactly why a mapping has no length and cannot be iterated or deleted wholesale.

The practical consequences: you cannot enumerate a mapping, return it, or place it in memory or calldata — it exists only in storage. If you need to iterate, keep a separate array of keys alongside it (the pattern OpenZeppelin's EnumerableMap automates). Nested mappings hash recursively (the inner slot becomes the outer key's value position). Reading a never-written key is not an error; it silently returns the zero value, so 'does this key exist?' usually needs an explicit boolean flag in the value.

mapping(address => uint256) balances;
// balances[alice] lives at:
// keccak256(abi.encode(alice) . slotOfBalances)

No table, no key list — every entry is a hash away.

Reading an unset key returns the zero value, never reverts. There is no built-in way to tell 'key absent' from 'key set to zero' — store an explicit exists flag if you need that distinction.