Section 01 · Definition
What is a mapping in Solidity?
A mapping is a key-value store. You give it a key and it gives you a value. It is the most-used data structure in Solidity by a wide margin.
Quick answer
What is a mapping? mapping(K => V) is Solidity's built-in key-value type. The compiler reserves one storage slot for the mapping itself, then computes an actual storage location on demand by hashing the key together with the slot number. Every possible key is implicitly present and starts at the default value of the value type — zero for numbers, address(0) for addresses, false for bools, '' for strings.
mapping(address => uint256) public balances;
mapping(address => bool) public allowed;
mapping(uint256 => address) public ownerOf; // ERC721 style
// Nested mapping — common for ERC20 allowance(owner, spender)
mapping(address => mapping(address => uint256)) public allowance;Declaring a mapping public gives you a free getter named after the mapping. Calling balances(someAddress) from a wallet returns the stored value. Inside the contract you read it as balances[someAddress].
Section 02 · How it works
The hash trick that makes lookups O(1)
A mapping does not store a list of entries. It stores nothing. Each access computes a deterministic storage slot from the key.
Imagine an unbounded sparse array indexed by every possible address. Most of the array is implicitly zero. Writing balances[alice] = 100 stores 100 at the address derived from keccak256(alice || slotOfBalances). Reading the same key reproduces the same hash and finds the same slot. There is never any list of who has been seen — the mapping has no notion of cardinality. To compute the actual value slot for a given mapping and key without writing a script, use the mapping calculator inside the Solidity Storage Layout Visualizer.
That is why you cannot iterate
There is nothing to iterate. The mapping has no list of keys, no internal counter, no way to ask 'what was inserted'. If you need enumeration — for example to pay every staker at the end of an epoch — you maintain a parallel address[] array and update it whenever you add a new participant.
Section 03 · Common patterns
Three patterns that show up in every contract
If you read any production contract, you will see these three mapping shapes within the first 40 lines.
// 1. Per-user state
mapping(address => uint256) public balances;
balances[msg.sender] += msg.value;
// 2. Mapping to a struct (the workhorse)
struct Position {
uint256 collateral;
uint256 debt;
uint64 openedAt;
}
mapping(address => Position) public positions;
positions[user].collateral += amount;
// 3. Nested mapping for two-key lookup
mapping(address => mapping(uint256 => bool)) public hasClaimed;
hasClaimed[msg.sender][campaignId] = true;Pattern 2 is by far the most common. A mapping to a struct gives you a clean way to bundle several fields under one identifier. Token contracts, lending pools, vaults, and prediction markets all use it.
Section 04 · Iterating safely
The enumerable mapping pattern
When you genuinely need to walk every entry, pair the mapping with an array of keys.
mapping(address => uint256) public balances;
address[] public users; // parallel index
mapping(address => bool) private isKnown;
function deposit() external payable {
if (!isKnown[msg.sender]) {
users.push(msg.sender);
isKnown[msg.sender] = true;
}
balances[msg.sender] += msg.value;
}
function totalUsers() external view returns (uint256) {
return users.length;
}Notice the safety check — push to the array only the first time we see this address, otherwise the array would grow on every deposit. OpenZeppelin's EnumerableSet ships exactly this pattern in audited form.
Beware: iterating a large dynamic array on chain can hit the block gas limit and brick the function. If you expect tens of thousands of entries, do the iteration off chain by reading events instead.
Section 06 · FAQ
Frequently asked questions
Can I iterate a mapping in Solidity?
No. The mapping does not store a list of keys, so there is no way to walk them. If you need iteration, maintain a parallel array of keys and push to it the first time each key is used. EnumerableSet from OpenZeppelin packages this safely.
What does balances[someUnknownAddress] return?
Zero. Every key in a mapping is implicitly present at the default value of the value type. For uint256 that is 0; for bool it is false; for address it is address(0). You cannot tell the difference between 'never written' and 'written and then set back to default'.
Is mapping cheaper than an array?
For lookups by key, yes — a mapping access is one keccak256 plus one SSTORE/SLOAD. An array lookup by index is similar in cost. The big difference is iteration: arrays can be walked for a known cost; mappings cannot be walked at all.
How do I delete an entry from a mapping?
Use the delete keyword: delete balances[user]. This resets that key to the default value of the value type, which refunds some gas. Note this does not remove the key from any parallel array of seen keys — you have to manage that separately.
Can mapping keys be a struct or another mapping?
No. The key must be a value type — uint, int, address, bool, bytesN, or a fixed-size enum. Reference types like string, bytes, struct, or mapping cannot be keys directly. If you need a composite key, hash the parts together with keccak256 and use the resulting bytes32 as the key.