Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Efficient Key-Value Lookups with Mappings

Hello! Welcome to your next lesson in the "Advanced Solidity & Contract Logic" module.

In our last session, we covered the critical topic of error handling with require, assert, and revert. In one of the examples, we briefly saw a mapping used to track balances. Today, we're going to dive deep into that concept.

This lesson is dedicated to learning how to use mappings to create efficient key-value data structures for lookups. Mappings are one of the most fundamental and widely used data structures in Solidity. For your goal of creating a cryptocurrency, understanding mappings is non-negotiable—they are the very mechanism we'll use to keep track of who owns how many of your tokens.

Given your background in backend development, the concept of a key-value store will be very familiar. Think of a Solidity mapping as being analogous to a Python dictionary, a Java HashMap, or a TypeScript Map.

What is a Mapping and Why is it Efficient?

At its core, a mapping is a data structure for storing and retrieving data using a unique key. The reason they are so efficient for lookups is that they are implemented as hash tables.

To understand this, let's look at the computer science concept that powers mappings.

How do Solidity Mappings work?

This article from Alchemy provides an excellent explanation of how hash tables work and directly relates them to Solidity mappings. It will give you a solid foundation for understanding why they are so efficient.

Read the first two sections, 'Mappings in Solidity' and the introduction to hash tables that precedes it. Focus on how a 'key' is passed through a 'hash function' to determine the storage location.

As the article explains, the hash function deterministically converts a key into a storage location. This means the EVM doesn't need to search through a list of items to find your data; it can calculate the exact location in a single step. This is known as O(1) or constant time complexity, the "holy grail" of search efficiency.

Hash Function Mapping Keys to Values
This diagram illustrates the core mechanism of a mapping. A key is passed to a hash function, which instantly calculates the index where the corresponding value is stored. This direct lookup is what makes mappings extremely fast.

To see the practical difference, contrast this with searching for an item in an array, which requires iterating through elements one by one—an O(n) operation that becomes very slow and expensive as the array grows.

Mapping | Solidity 0.8

This short clip from Smart Contract Programmer vividly illustrates the efficiency of a mapping lookup compared to an array search.

Watch from 00:11 to 01:14. Notice the comparison between iterating through an array versus a single lookup in a mapping.

Working with Mappings in Solidity

Now let's get into the syntax and practical use of mappings.

Declaration

You declare a mapping as a state variable using the following syntax:

mapping(_KeyType => _ValueType) public name;

  • _KeyType: Can be any of Solidity's built-in value types (like address, uint, bytes32), but it cannot be a complex type like another mapping, a struct, or an array. The key type is not actually stored; only its keccak256 hash is used to find the location of the value.
  • _ValueType: Can be any type, including other mappings (which creates a nested mapping), arrays, or structs.

For our token, the most important mapping will be the one that tracks user balances:

// This mapping will store the token balance for each user address.
mapping(address => uint) public balances;

Here, the key is the user's Ethereum address, and the value is the uint representing their token balance.

Basic Operations: Set, Get, Update, Delete

Interacting with a mapping is straightforward and uses a familiar square-bracket syntax.

Mapping | Solidity 0.8

This video provides a clear, step-by-step demonstration of the fundamental operations you'll perform with mappings.

Watch from 01:14 to 05:35. This covers declaring a mapping and then how to set, get, update, and delete values within it.

Let's summarize those operations using our balances mapping:

contract Token {
    mapping(address => uint) public balances;

    // A function to assign an initial balance (e.g., for testing)
    function setBalance(address _user, uint _amount) public {
        // SET a value
        balances[_user] = _amount;
    }

    // A function to increment a user's balance
    function increaseBalance(address _user, uint _increment) public {
        // UPDATE a value
        balances[_user] += _increment;
    }

    // A function to reset a user's balance to zero
    function clearBalance(address _user) public {
        // DELETE a value
        // This resets the value at the key to its default (0 for uint)
        delete balances[_user];
    }

    // To GET a value, you can call the public 'balances' getter function
    // that Solidity automatically creates for you.
    // For example, from an external script: `let balance = await contract.balances(userAddress);`
}

A Crucial Detail: Default Values

In languages like Python or Java, trying to access a key that doesn't exist in a dictionary or hashmap would throw an error or return null. Solidity works differently.

In Solidity, all possible keys in a mapping are considered to exist from the start, and they are initialized to a default value (a byte-representation of all zeros). Accessing an unassigned key will simply return this default value.

  • uint: 0
  • bool: false
  • address: 0x0000000000000000000000000000000000000000

This is a critical behavior to understand to avoid bugs.

Solidity Tutorial: Mappings (simple mappings, nested mappings, array in mappings...)

This is a very important concept. The following video segment explains it perfectly by contrasting it with JavaScript's undefined, which you'll find relatable.

Watch from 04:09 to 05:50. Pay close attention to the explanation of why an unassigned key returns a default value like 0 or false.

A Key Limitation: Mappings Are Not Iterable

Another major difference from data structures in other languages is that you cannot iterate over a mapping. You cannot get a list of all keys or all values. This is a design choice tied to the vast, virtually initialized nature of mappings. Since all possible keys "exist," looping through them is impossible.

If you need to track a list of users or items, the common pattern is to use a mapping in conjunction with an array, where the array stores the keys that have been added. We will explore arrays in a later lesson.

Advanced Patterns and Real-World Use Cases

The true power of mappings comes from how they structure the core logic of smart contracts.

The Heart of an ERC-20 Token: balanceOf and allowance

As we've discussed, mapping(address => uint) public balanceOf; is how every ERC-20 token tracks who owns what. It's not just a theoretical example; it's production code.

How do Solidity Mappings work?

Let's see this in the wild. This section of the Alchemy article points directly to the DAI stablecoin contract on the Ethereum mainnet, showing its balanceOf mapping.

Read the section 'Mappings in Production: ERC-20 Tokens'. This will directly connect what you're learning to your end goal.

Another core function of tokens is allowing one user to approve another user to spend tokens on their behalf. This requires a more complex data structure: a nested mapping.

mapping(address => mapping(address => uint)) public allowance;

Here's how to read this:

  • The first address is the token owner.
  • The second address is the spender they are approving.
  • The uint is the amount they are allowed to spend.

So, allowance[owner][spender] would return the approved amount.

Solidity Tutorial: Mappings (simple mappings, nested mappings, array in mappings...)

The concept of nested mappings can seem tricky at first, but this video explains it using the exact ERC-20 allowance example.

Watch from 05:50 to 08:48. This will show you how to declare and manipulate a nested mapping.

Test your understanding!

Imagine you're building a simple on-chain blogging platform. You want to track which users have "liked" which posts. Each post is identified by a uint postId.

How would you declare a mapping named likes to track this? A user should only be able to like a post once.

Show answer

A nested mapping is the perfect tool for this. The value type should be a bool to track the "liked" status.

// The first key is the post ID.
// The second key is the user's address.
// The value is 'true' if the user has liked the post, and 'false' otherwise.
mapping(uint => mapping(address => bool)) public likes;

To record a like, you would write:
likes[postId][msg.sender] = true;

To check if a user has liked a post, you would check:
if (likes[postId][msg.sender] == true) { ... }

Conclusion

You've now mastered one of the most essential data structures in Solidity. Efficiently managing state is key to building scalable and cost-effective smart contracts, and mappings are your primary tool for that job.

Comparison of Struct, Array, and Mapping Features in Solidity
This table provides a concise summary of Mappings, comparing them to Arrays and Structs. Notice the key features of a Mapping: key-value storage, unordered, and highly efficient for lookups.

Key Takeaways:

  • Mappings are Solidity's hash table implementation, providing highly efficient O(1) key-value lookups.
  • They are the standard way to track data associated with user addresses, such as the balanceOf and allowance mappings in an ERC-20 token.
  • Unlike dictionaries in other languages, uninitialized keys return a default value (e.g., 0, false), and mappings cannot be iterated.
  • Nested mappings allow for more complex, multi-dimensional data relationships.

Preview of the Next Lesson:
We've seen how to store a single value (like a uint balance) for a key. But what if we need to store a group of related data for each key? For example, what if for each token holder, we wanted to track not just their balance, but also the date they first joined and their voting power? In the next lesson, we will cover structs, which allow you to define your own custom, complex data types to handle exactly this kind of scenario.

Can't find a good explanation? Sign up and we'll make it for you

Sign up