How Do Mappings Work in Solidity?


Hey there! If you’ve been exploring Solidity, you’ve likely encountered the term mapping. It’s a unique feature of Solidity, especially when managing data in smart contracts. Let’s dive deep into mapping.

What is mapping?

At its core, mapping is a data structure that lets you store key-value pairs. Think of it as a dictionary or hash table where each unique key is associated with a specific value.

Here’s a simple example:

mapping(address => uint256) public balances;

In this case, we’re creating a mapping where Ethereum addresses (the keys) are linked to unsigned integers (the values), representing the balance of tokens for each address.

Why Use mapping?

  • Efficiency: Fetching a value using its key in a mapping is super fast, no matter how many entries you have. This speed is vital for blockchain operations where you’re mindful of computation costs.
  • Flexibility: mapping can be nested, allowing for complex data structures. For example, a mapping can be inside another mapping.
  • Dynamic Size: Unlike arrays, mappings don’t have a fixed size. It can grow as needed, which is especially useful in scenarios where the total number of entries isn’t known in advance.

🤔 Even though a mapping can grow without limits, it doesn’t store data in a linear structure. Instead, it uses a hash table mechanism, ensuring high efficiency.

Some Caveats to Remember

While mapping is powerful, there are some nuances to keep in mind:

  1. Default Values: In Solidity, every possible key exists in a mapping by default and is mapped to a value. For types like uint, the default value is 0, and for bool, it’s false. This means you can’t check for the existence of a key by checking its value against the default.
  2. No Key Iteration: You can’t iterate over the keys or values of a mapping directly. If you need to know all the keys used in your mapping, you’ll need to maintain a separate list or array.
  3. Key Storage: The keys in a mapping aren’t stored in a way that you can retrieve them. The value is derived from a hash of the key, so if you lose track of a key, you can’t recover it from the mapping.
  4. Gas Considerations: While mapping provides constant-time access, which is gas-efficient, remember that updating the state of the Ethereum blockchain (like changing a value in a mapping) costs gas. Always be mindful of contract operations and their associated gas costs.
  5. Data Overwrite: If you set a value for an existing key in a mapping, it will overwrite the previous value without any warning. Always ensure that you’re not unintentionally overwriting important data.
  6. Nested Mappings: You can have mappings inside of mappings. This can be useful for more complex data structures but can also make the contract harder to read and understand. Use nested mappings judiciously.
  7. External Data Access: Data in a mapping is not automatically accessible to external callers unless you provide a function to do so. For public visibility, the compiler automatically generates a getter function.
  8. Clearing Mappings: There’s no direct way to clear or reset a mapping in its entirety. You’d have to reset each key-value pair individually, which could be gas-intensive.

🤔 The Ethereum Virtual Machine (EVM) doesn’t actually store the keys of your mapping. Instead, it uses the key to compute a keccak256 hash, which then points to the value. This is why accessing data in a mapping is so fast and why the keys aren’t directly retrievable.

Practical Use Cases

mapping is versatile and finds its place in various scenarios:

  • Token Balances: As seen in the example above, mapping is often used to track the balance of tokens for each address in ERC-20 token contracts.
  • Voting Systems: You can use mapping to track whether a particular address has voted in a poll or to store the votes each option received.
  • Access Control: mapping can be used to store roles or permissions associated with specific addresses.

Consider a scenario where we want to create a basic token system. Users can be awarded tokens, and we also want to track if a user is an admin.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

contract SimpleTokenSystem {
    // Mapping to track user balances
    mapping(address => uint256) public balances;

    // Mapping to track admin users
    mapping(address => bool) public isAdmin;

    // Award tokens to a user
    function awardTokens(address _user, uint256 _amount) public {
        // Only an admin can award tokens
        require(isAdmin[msg.sender], "Only admins can award tokens!");

        balances[_user] += _amount;
    }

    // Make a user an admin
    function makeAdmin(address _user) public {
        // Only an admin can make another user an admin
        require(isAdmin[msg.sender], "Only admins can assign admin roles!");

        isAdmin[_user] = true;
    }

    // Constructor to set the contract deployer as the first admin
    constructor() {
        isAdmin[msg.sender] = true;
    }
}

In this contract:

  • We have two mapping structures: balances to track the number of tokens each user has, and isAdmin to check if a user is an admin.
  • The awardTokens function allows an admin to award tokens to users.
  • The makeAdmin function lets an admin grant admin status to other users.
  • The constructor ensures that the person deploying the contract is set as the first admin.

🤔 Did you know? While mappings are great for quickly accessing data with a known key, they don’t let you loop over keys or values.

Nested Mappings in Solidity

In Solidity, you can have mappings inside of mappings, creating what’s known as nested mappings. This allows for more complex data structures, enabling developers to associate a key with another mapping as its value. Nested mappings can be particularly useful when you want to associate two different types of keys with a specific value.

Practical Example: A Voting System

Imagine you’re creating a voting system where users can vote for candidates in different categories. You might want to use a nested mapping to track votes. The outer mapping could use the category as the key, and the inner mapping could use the candidate’s address as the key and the number of votes as the value.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

contract VotingSystem {
    // Outer mapping with category as the key and the inner mapping as the value
    mapping(string => mapping(address => uint256)) public votes;

    // Cast a vote
    function castVote(string memory category, address candidate) public {
        votes[category][candidate] += 1;
    }

    // Get votes for a candidate in a category
    function getVotesForCandidate(string memory category, address candidate) public view returns (uint256) {
        return votes[category][candidate];
    }
}

In this example, the castVote function allows users to vote for a candidate in a specific category. The getVotesForCandidate function retrieves the number of votes a candidate has received in a given category.


To wrap things up, mapping is a powerful tool in Solidity. It’s efficient, flexible, and dynamic, making it perfect for a range of blockchain applications. As we keep exploring Solidity, it’s features like mapping that highlight the language’s capabilities. Stay curious and happy coding!