Skip to main content
Create your own

Efficient Data Structures: Mappings and Arrays

Welcome back! In our last session, we focused on implementing logic with conditionals and loops, paying close attention to the gas costs of every operation. You learned that iterating over large datasets on-chain is a primary cause of high transaction fees and a significant security risk. This sets the stage perfectly for today's topic.

This lesson addresses how to store and manage data efficiently in Solidity. We will explore the two most fundamental data structures: mappings and arrays. Your background in data warehousing and SQL has given you a deep appreciation for structured data and the importance of efficient retrieval through indexing. We'll apply that same thinking here, where mappings act as the EVM's highly optimized, built-in index. You will learn not just what they are, but why they are the cornerstone of almost every token contract, including the stablecoins and tokenized securities you aim to build.

By the end of this lesson, you will be able to choose the appropriate data structure for a given task and implement both mappings and arrays for efficient on-chain data storage and retrieval.

1. Mappings: The EVM's Key-Value Store

At its heart, a blockchain ledger is a key-value store. An address (the key) is associated with a balance (the value). Solidity's mapping type provides a direct and highly efficient way to implement this pattern.

A conceptual illustration of a key-value data structure, where unique keys are paired with corresponding values. This is the fundamental model for a Solidity mapping.

A mapping is declared with the syntax mapping(_KeyType => _ValueType) public myMapping;. You can think of it like a hash table or a dictionary in Python.

The EatTheBlocks channel provides an excellent introduction to the syntax and basic operations for mappings.

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

This video covers the fundamental concepts of Solidity mappings, including their declaration, basic operations, and the important concept of default values.

Please watch the following segments to get a solid grasp of the basics: Introduction: The video begins by comparing mappings to JavaScript objects and highlights their main advantage (easy retrieval) and disadvantage (not iterable). Basic Operations: Watch how to declare, add, read, and update elements in a mapping. Default Values: This is a critical concept. Pay attention to how Solidity handles requests for keys that don't exist. Unlike a database that might return null or an error, Solidity returns the default value for the value type (e.g., 0 for uint, false for bool).

2. Efficiency: Mappings vs. Arrays

In the previous lesson, you saw that looping over an array to find an element is expensive. Mappings solve this problem.

  • Arrays have O(n) lookup complexity. To find an item, you may have to iterate through every single element, and the gas cost grows linearly with the size of the array.
  • Mappings have O(1) lookup complexity. Accessing a value by its key is a constant-time operation, meaning the gas cost is the same whether the mapping holds one entry or one million.

This efficiency is why mappings are the preferred choice for tasks like tracking token balances. Searching an array of a million token holders for one person's balance would be computationally infeasible on-chain, but a mapping can retrieve it instantly.

The following reading from an Alchemy article explains this crucial difference.

12 Solidity Gas Optimization Techniques

This article details several gas-saving patterns. We'll focus on its direct comparison of mappings and arrays.

Please read the section Use mappings instead of arrays. It clearly explains the O(n) vs. O(1) difference and provides a code example that makes the gas impact tangible. Also, quickly review the FAQ entry that summarizes this point.

So, how do mappings achieve this? Under the hood, the EVM computes the storage location of a value by hashing the key and the mapping's slot number using keccak256. This calculation directly yields the memory address where the value is stored, eliminating any need for searching. This is also why mappings are not iterable—the keys are not stored sequentially, so the EVM has no list of keys to loop through.

This diagram shows how the storage slot for a mapping value is calculated. The key (e.g., `30`) and the mapping's base slot (e.g., `2`) are hashed with `keccak256` to compute the exact storage location, enabling O(1) lookups.

3. Practical Application: A Simple Deposit Ledger

Let's apply this knowledge to a practical example that is fundamental to tokenized money systems: tracking user deposits. We will build a simple contract that securely keeps a record of how much Ether each user has deposited.

The following tutorial walks through the process of creating a simple deposit contract, starting with an insecure version and then securing it with a mapping. This is a perfect example of "learning by doing."

Solidity Mappings - Ethereum Blockchain Developer

This tutorial from Ethereum Blockchain Developer provides a hands-on example of using a mapping to secure a smart contract by tracking individual user balances.

Follow the tutorial through these sections to see the evolution of the contract: Read the introduction to understand the initial, insecure contract. It allows anyone to deposit money, but also allows anyone to withdraw all the money. Next, read the solution. Pay close attention to how the mapping(address => uint) public balanceReceived; is added and used in the sendMoney and withdrawAllMoney functions. Notice the use of msg.sender as the key and msg.value as the amount to update the balance. This is the core pattern for any token ledger. Finally, read the enhancement for partial withdrawals. This introduces a require check against the user's balance in the mapping, a pattern you'll use constantly to enforce rules in your contracts.

4. Solving the Iterability Problem

The biggest drawback of mappings is that they are not iterable. What if you need to get a list of all token holders or all whitelisted investors?

The standard solution is a compound pattern: use a mapping for fast O(1) lookups and a parallel dynamic array to store the keys.

When a new entry is added:

  1. The key-value pair is added to the mapping.
  2. The key is push-ed onto the array.

This gives you the best of both worlds: fast lookups via the mapping and the ability to retrieve a list of all keys from the array. The DesignCourse video below masterfully demonstrates this exact pattern.

06. Solidity Mappings & Structs Tutorial

This video builds a small "Courses" contract to demonstrate how to combine structs, mappings, and arrays to create a practical, iterable data store.

Watch the following segments to see how this powerful pattern is constructed: Defining the Mapping: The instructor defines a mapping from an address to a custom Instructor struct. The Key Array: Here, the instructor explains why a separate public array of addresses is needed to make the list of instructors accessible. This is the core of the iterable mapping pattern. The set Function: Observe how the setInstructor function performs two actions: it adds the data to the mapping and pushes the address key into the instructorAccounts array. The get Functions: Finally, see how the getInstructors function simply returns the entire array of keys, providing the list of all instructors.

5. Advanced Mapping Patterns

As your contracts grow in complexity, you'll encounter more advanced mapping structures. Two are particularly common:

  1. Nested Mappings: A mapping where the value is another mapping. The classic example is the approve function in an ERC-20 token, which records how much a spender is allowed to withdraw from an owner's account. This is represented as: mapping(address => mapping(address => uint)) public allowance; where the first key is the owner and the second is the spender.

  2. Mapping to Arrays: A mapping where the value is an array. This can be useful for grouping items under a specific key, for example, mapping(uint256 => uint256[]) to store all token IDs owned by a user in an NFT project.

The EatTheBlocks video you watched earlier also covers these patterns, which you may want to revisit as you encounter them in practice.

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

This video also demonstrates how to implement nested mappings and mappings that contain arrays.

You can review these advanced patterns as needed: Nested Mappings: This section shows the syntax for a mapping within a mapping, using the ERC-20 allowance structure as a perfect example. Array inside a Mapping: This part demonstrates how to declare and manipulate an array that serves as the value in a mapping.

Conclusion

In this lesson, we explored Solidity's primary data structures and established clear guidelines for their use. Your experience with database design should make these principles feel intuitive: use the right tool for the job to ensure efficiency and scalability.

Here are the key takeaways:

  • Mappings are the default choice for key-value data due to their O(1) lookup efficiency and lower gas cost. They are essential for tracking balances, ownership, and access control lists.
  • Arrays should be used when you need to maintain a specific order or iterate over a collection of items, but always with extreme caution regarding gas costs and unbounded loops.
  • The non-iterability of mappings is their main limitation. The standard solution is the iterable mapping pattern, which uses a parallel array to store the mapping's keys.
  • The efficiency of mappings is not magic; it stems from the EVM's use of keccak256 hashing to directly compute the storage location of a value from its key.

Now that you can effectively store and manage your contract's state, our next lesson will focus on how to communicate those state changes to the outside world. We will cover events, which allow your smart contract to emit logs that can be captured by user interfaces, analytics dashboards, and data indexing services—a crucial bridge between the on-chain world and the off-chain tools you are familiar with from your work in business intelligence.

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

Sign up