Understanding Mapping vs. Array in Solidity

Zach
November 23rd, 2021

If you already have some experience as a developer, many of the terms and concepts you encounter when first diving into Solidity will hopefully be familiar to you, such as Solidity's Array type, which is very similar to arrays found in other programming languages:

string cars [];
cars = ['volvo', 'ford', 'tesla', 'honda'];

However, Solidity also has a Mapping type, which may seem similar to an array at first since it is also used to store a group of data. You'll see it take a syntax such as this:

mapping (address => uint) highscores;

However, there are some key differences between mapping and array in Solidity that are important to understand as you progress in your journey as a smart contract developer.

In this article, we'll be taking a look at both types and discussing their differences, and explaining (with examples) when to use one instead of the other.

To begin, let's take a quick look at the array type:

Arrays

An array in Solidity is much like an array in any other programming language. For example, creating an array of fruit in Solidity might look like this:

string fruit[];
fruit = ["apple", "mango", "watermelon"];

You'll notice that array declarations require a value type, such as string, uint, or even a struct. This is because Solidity is a statically typed language.

Just like arrays in other languages, arrays in Solidity also have some methods available for reading and editing them:

string fruit[];
fruit = ["apple", "mango", "watermelon"];
// let's add another fruit to the array...
fruit.push("bacon");
// updated array: ["apple", "mango", "watermelon", "bacon"]
// wait! bacon isn't a fruit... let's get rid of that last entry real quick:
fruit.pop();
// updated array: ["apple", "mango", "watermelon"]

Note: Unfortunately, the above methods are the only array methods available in Solidity, other than the .length method, a read-only method that returns the length of the array. It could be said that Solidity arrays have far fewer methods than arrays in other languages because blockchain data is rigid by design, and not meant to encourage that any of its data be altered or erased.

Pretty simple stuff right? I bet you're breezing right through 😎

Arrays: Fixed vs. Dynamic length

One final thing to mention about arrays before moving on is that an array in Solidity can have either a fixed or dynamic length. In the example above, our array assumes a dynamic length because no fixed length was declared. This means our array can be any of length and thus can be edited freely.

However, if we wanted our array to only ever have 3 fruits inside of it, we would give it a fixed length when we declared it, like so:

string fruit[3];
fruit = ["apple", "mango", "watermelon"];
// try to add another fruit to the array...
fruit.push("pineapple");
// ERROR: array "fruit" is of fixed length 3. You may not add another fruit!

Alright, so I think we've done a good job covering the basics of an array.

Now let's move on and discuss the mapping type:

Mappings

A mapping is similar to an array in that it is a Solidity reference type meant for storing a group of data. However, its syntax and structure are quite different, in a way that allows it to serve a unique and important purpose. For example, take this mapping, which could be used to store the highscores of each player of a game:

mapping (address => uint) highscores;

Simply put, a mapping is a table of keys and values (each with a pre-defined type).

You can think of the mapping above as initiating an empty table that lives inside of your smart contract, and is waiting to be filled in with whatever data you want:

addresshighscore

Now let's add some data to our highscores mapping, and see what happens:

mapping (address => uint) highscores;
address playerOne = "0xf39Fd6e51aad23F6F4cd6aB9927279cggFb91166";
highscores[playerOne] = 750;

Let's check out our table visualization again:

addresshighscore
0xf39...166750

Easy right? Now let's talk about why the mapping type is so important, and what makes it different from an array.

Mapping: A Closer Look

When a mapping is created, it assumes that every possible key exists, and is mapped to some value. In other words, unlike an array, a mapping does not have a retrievable length, or any real concept of a key or value being "set".

Note: Technically, a mapping is a hash table - however, knowing all the details of hash tables and how they work is very "under the hood" stuff and isn't a requirement for using the mapping type effectively.

Don't worry - when we say that "every possible key exists", it doesn't mean that you have no control over your mapping, or that it comes with a bunch of random key-value pairs already built into it. It just means that a mapping is not structured in the same way as an array, and thus cannot be expected to behave like one.

For example, to fetch a value from your mapping, you must have that value's key. If we forgot what playerOne's highscore was and wanted to fetch it, we would need to first know playerOne's address:

address playerOne = "0xf39Fd6e51aad23F6F4cd6aB9927279cggFb91166"
uint playerOneHighScore = highscores[playerOne];
// playerOneHighScore = 750

If we did not have a player's address, then we would have no way of knowing their highscore, and in theory their highscore would be irretrievable to us forever. Whereas with an array, as a last-ditch effort we could at least loop through the array and print all of the highscores and see if any of them looked familiar. But you cannot loop through a mapping.

"So what's the point? Isn't a mapping just a crappy array then?"

No, not at all! Due to the way that a mapping is structured, fetching a piece of data from a mapping (if you have the data's key) is far more efficient than fetching the same data from an array. To fetch data from an array requires iterating over the whole array until you find the element you're looking for, but a mapping will run and grab that data right away (again, so long as you have the key).

This makes mapping the ideal type for storing basic information about an address, for example, since often there is no need to store the address yourself because it is provided to you by whoever is interacting with your smart contract (in the form of msg.sender).

And unfortunately in the world of Ethereum, the fact that transactions that edit data on the blockchain require gas fees means that storing/retrieving data from a smart contract as efficiently as possible can save you and your users lots of money in the long run.

Mapping: Default values

You might be wondering what a mapping will return as a default value for any key that hasn't had a value explicitly set for it yet.

For example, imagine if we searched for the highscore of playerTwo, which was never added to the mapping.

Because our mapping is storing its values as type uint, the default value we would get back is 0:

address playerTwo = "0x0Cca37CBaeE672d8429B148DF67cB3513422925c";
uint playerTwoHighScore = highscores[playerTwo];
// playerTwoHighScore = 0, because we never added a score for them inside the mapping!

This is because all types in Solidity have a default value if no value is assigned to them, and the default value for uint is 0.

This is true for all types in general, not just for values inside of a mapping. To quote the Solidity documentation:

The concept of "undefined” or “null” values does not exist in Solidity, but newly declared variables always have a default value dependent on its type.

If you're wondering, in Solidity the default value for each type is:

  • uint:0
  • int:0
  • fixed:0.0 (warning: not fully supported)
  • string:""
  • boolean:false
  • enum: the first element of the enum
  • address:0x0
  • array: a dynamically-sized []
  • mapping: an empty mapping
  • struct: a struct where all members are set to default values
  • function: if internal, an empty function. If external, a function that throws an error when called.

Mapping vs. Array: When to use one over the other

If you need to be able to iterate over your group of data (such as with a for loop), then use an array.

If you don't need to be able to iterate over your data, and will be able to fetch values based on a known key, then use a mapping.

However, sometimes it is optimal to use both. Since iterating over an array can be an expensive action in Solidity (compared to fetching data from a mapping), and since you may want to be able to store both a value and its key within your smart contract, developers sometimes opt to create an array of keys, which serve as a reference to even more data that can then be retrieved from its associated value inside of a mapping.

Keep in mind that you should never allow an array in Solidity to grow too large, since in theory iterating over a big enough array could end up costing more in gas fees than the value of the transaction is worth (another reason to consider using mapping when possible).

Next steps

To learn more about Solidity and its types, I recommend starting with the Solidity documentation.

If you are brand new to Solidity and are looking for more resources to get started, I couldn't recommend these resources enough:

If you liked this post, consider following me on twitter and/or subscribing to the blog using the form below.

Recommended Posts

HubSpot Forms in React: Submit a form using the HubSpot API

In this post you will learn how to submit HubSpot forms from your React website using the HubSpot API.

Read more

React: Recreating the Hacker News comments section

In this post we'll use React to recreate the Hacker News comment section using a recursively generated comment tree.

Read more