Track 4 · Solidity foundations · lesson 6
Mappings
9 min
Track 4 · Solidity foundations · lesson 6
9 min
Every token balance in existence is a mapping. Every allowance, every ownership record, every "has this address already claimed?" check. If you learn one data structure properly, make it this one.
mapping(address => uint256) public balances;
Key to value. Read it with balances[someAddress], write it the same way.
A mapping is not a hash table you can walk. There is no length, no list of keys, and no way to iterate it.
Every possible key already "exists" and returns the zero value. Storage is a
field of zeros, and balances[anyAddressAtAll] returns 0 without anything
having been stored. Nothing distinguishes "never set" from "set to zero".
The absence of iteration is not laziness in the language design. A mapping's keys are hashed into storage slots and the keys themselves are never stored — there is no list to walk because no list was ever kept.
If you need to enumerate, you keep a separate array alongside and maintain both. That is a real pattern, and it comes with a real cost: an array you loop over grows without bound, and eventually the loop costs more gas than a block allows. Contracts have been frozen exactly that way.
Predict
Allowances need two keys — owner and spender:
mapping(address => mapping(address => uint256)) public allowance;
Read as allowance[owner][spender]. This exact line is in every ERC-20 token,
and you will write it from scratch in track 5.
Check
Because unset and zero are indistinguishable, a mapping cannot answer "has this
address registered?" using the value alone. registered[addr] == 0 is true for
someone who registered with a zero amount and for a total stranger.
The fix is an explicit flag — mapping(address => bool) hasRegistered — or a
struct with an exists field. It looks redundant right up until the moment it
is the only thing standing between you and an exploit.