Back to glossary

Mapping in Solidity

Table of Contents

What is mapping in Solidity

A mapping in Solidity is a key-value data structure that associates unique keys with corresponding values. A key-value pair is an association where a unique identifier (the 'key') points to an associated piece of information (the 'value'). 

In this structure, each key (like an ID or address) maps to a specific value (like a balance or status), allowing efficient lookups and data retrieval. 

Mappings in Solidity are analogous to hash tables or dictionaries in other programming languages. 

Mappings are declared with the syntax mapping(KeyType => ValueType), where KeyType can be a specific value type (e.g., address, uint, bytes32), and ValueType can be any valid Solidity data type, including complex structures or other mappings.

Characteristics of Solidity mappings

Mappings in Solidity have distinct properties that differentiate them from other data structures, influencing how they can be used and how they impact performance in smart contracts:

  • No iteration: Mappings do not store keys explicitly, and there is no built-in way to list all keys or values stored within a mapping. This is because mappings are stored in a location derived from the keccak256 hash of the key concatenated with the mapping's storage slot, rather than as traditional hash tables. This implementation provides efficient, constant-time lookups regardless of size but prevents iteration over keys, as only the hashed storage locations are used. It is possible to create an iterable mapping by maintaining a separate array of keys alongside the mapping to track and iterate through entries.
  • No length property: Unlike arrays, mappings do not have a length property. As a result, developers must maintain a separate counter or array to track entries.
  • Storage-only: Mappings can only be stored in contract state variables (storage) and cannot be used as parameters or return types in memory. This is because mappings can be arbitrarily large and don't have a defined layout in memory. Meaning, accessing a non-existent key will never revert, it simply returns a default value.
  • Default values: If a key has not been assigned a value, accessing it returns the default value of ValueType (e.g., 0 for uint, false for bool, or address(0) for address). 

Syntax of mapping in Solidity

mapping(KeyType => ValueType) public variableName;  

Example

mapping(address => uint256) public balances; 

Reading and writing values

Here is a small, complete code example demonstrating how to declare a mapping, assign a value to a key, and retrieve a value from a key:

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

contract UserStatusTracker {
    // Declares a public mapping to track the active status of user addresses
    mapping(address => bool) public isActive;

    // Events to log state changes (best practice)
    event AccountActivated(address indexed user);
    event AccountDeactivated(address indexed user);

    // Function to set the caller's account status to active
    function activateMyAccount() public {
        // Assigns 'true' to the 'isActive' status for msg.sender's address
        isActive[msg.sender] = true;
        emit AccountActivated(msg.sender); // Emit event for activation
    }

    // Function to check the active status of any given address
    function getStatus(address _user) public view returns (bool) {
        // Retrieves the boolean value associated with '_user'
        return isActive[_user];
    }

    // Function to deactivate the caller's own account
    function deactivateMyAccount() public {
        // Sets the 'isActive' status for msg.sender to 'false'
        isActive[msg.sender] = false;
        emit AccountDeactivated(msg.sender); // Emit event for deactivation
    }
}

Nested mappings

Mappings can nest other mappings:

mapping(address => mapping(uint256 => bool)) public approvals;  

How mappings work under the hood

Mappings are implemented using a cryptographic hashing mechanism. When you define a mapping, Solidity does not create actual slots for every possible key. Instead, the storage location for a specific value is derived by computing the keccak256 hash of the key concatenated with the mapping's storage slot. This means that:

  • No storage is allocated unless written to: Actual storage is only consumed on the blockchain when you assign a value to a key in a mapping.
  • Deterministic storage layout: The exact storage slot for any given key is consistently calculated, allowing for efficient, constant-time lookups.

This underlying behavior explains why mappings cannot be iterated over and why unused keys always return their default values rather than reverting.

Use cases

Mappings are one of the most widely used data structures in Solidity due to their efficient key-value lookups. They appear in virtually all types of smart contracts and serve many practical purposes. The following common use cases demonstrate how mappings solve different problems for blockchain applications:

  • Ownership tracking: Storing token balances (address → uint256) in ERC-20 tokens or NFT ownership in ERC-721 contracts.
  • Access control: Managing permissions and roles (address → bool).
  • Lookup tables: Associating identifiers with data (uint256 → string).

An example of mapping in a Solidity contract can be found here.

Mapping key and value types

Key type constraints

In Solidity mappings, not all types can be used as keys. Valid key types include:

  • Value types (elementary types): These terms are often used interchangeably in Solidity. Value types are the most basic data types, which are always copied when assigned or passed as arguments, directly corresponding to a fixed size in memory or storage. This includes bool, int/uint variants, address, fixed-size byte arrays (bytes1 to bytes32), and enums.
  • Contract types: Any user-defined or built-in contract type. For example, a mapping could use a contract address as a key to associate data with specific deployed contract instances: mapping(MyContract => uint256) public contractBalances;
  • String literals: While the string type cannot be used directly as a mapping key, developers often convert strings to a fixed-size format using a cryptographic hash function. The keccak256 function (Ethereum's cryptographic hash function) generates a bytes32 hash, which can then be used as a valid mapping key: mapping(bytes32 => address) public usernameHashes;

Key types that are not allowed as keys include:

  • Dynamic arrays (bytes, string): Cannot be used as keys due to their variable size and complex internal representation.
  • Structs: Custom data structures are too complex to serve as direct mapping keys.
  • Mappings: Mappings cannot be keys for other mappings.
  • Reference types: Complex types whose actual data might reside elsewhere in memory or storage are unsuitable for direct key hashing.

Value type flexibility

In contrast to keys, value types in mappings can be virtually any Solidity type. This provides significant flexibility in structuring data. 

Allowed value types include:

  • Simple types: bool, int/uint variants, address, bytes, string
  • Complex types: structs, arrays (fixed and dynamic)
  • Reference types: Reference types are data types that do not directly store their value but instead hold a reference (memory address or storage slot) where the data is stored. This includes other mappings (enabling nested mappings).
  • User-defined types - custom structs, enums, contract types

Default values

Each value type has a specific default value that is returned when accessing a mapping key that has not been explicitly assigned a value. Accessing an unassigned key never causes an error; it simply returns this default value.

Table illustrating Value Types and their default values.
Figure: Table structure showing the default values by value type

For a comprehensive list and detailed explanations of default values across all Solidity types, refer to the official Solidity documentation.

Limitations of mappings

Mappings, while powerful, come with certain inherent limitations:

  • No key enumeration: Separate variables are needed to track keys. Meaning there is no direct way to obtain a list of all keys that have been set in a mapping.
  • Gas costs: While lookups are efficient, storing data in mappings can be expensive due to storage costs, especially when using complex value types that require multiple storage slots.
  • No direct serialization: Mappings cannot be directly serialized or passed between contracts. Serialization refers to the process of converting a data structure into a format that can be stored or transmitted. For mappings, individual values must be accessed and transferred one by one for contract-to-contract communication or off-chain usage.
  • No direct copying: You cannot directly copy one mapping to another. If you have two mappings, say mappingA and mappingB, you cannot simply write mappingA = mappingB; to copy all contents. Instead, each key-value pair must be manually copied from one mapping to the other, typically by iterating over tracked keys.

Best practices and caveats for Solidity mapping

When working with mappings in Solidity, developers should consider best practices to ensure efficiency and security:

  • Key validation: When using derived keys (e.g., hashed strings), use a consistent hashing approach throughout your contract. 
  • Complexity: Avoid unnecessary complexity in mapping key or value types unless strictly required by the application logic. Simpler types result in lower gas costs.
  • Testing edge cases: Thoroughly test contract functions that interact with mappings. Focus on cases where keys might not exist, values are at their default.
  • Iterable mappings:  If iteration is a functional requirement, you'll need to store keys separately in an array to enable looping through them. Be aware this incurs additional gas and storage costs, so implement this pattern only when truly necessary.

Related Terms

No items found.