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

Array Fundamentals

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

In our last lesson, we explored structs, which are fantastic for bundling related data together, like an Investor profile. We saw how the mapping(address => Investor) pattern lets us create a database-like structure. However, mappings are unordered and can't be iterated over directly. What if you need to keep an ordered list, like a history of the last 10 investors or a list of whitelisted addresses?

This brings us to today's topic. Your goal for this lesson is to learn how to work with arrays (fixed-size and dynamic) to store collections of data.

For someone with your background in languages like Python, Java, or TypeScript, the concept of an array will be very familiar. In Solidity, as in those languages, an array is a data structure used to store an ordered collection of elements. The key differences lie in how they are stored (on the blockchain), their associated gas costs, and the strict distinction between different types of arrays.

Understanding the Two Types of Solidity Arrays

Solidity provides two fundamental types of arrays, and choosing the right one is crucial for both functionality and gas efficiency.

  1. Fixed-Size Arrays: The length of these arrays is determined when you write the contract (at compile-time) and can never be changed. They are useful when you know exactly how many items you need to store.

    • Syntax: uint[10] public adminWallets; // An array that holds exactly 10 uints.
  2. Dynamic Arrays: These arrays have a size that can change during the contract's lifetime. You can add or remove elements, making them flexible for situations where the amount of data is unknown.

    • Syntax: address[] public investors; // An array of addresses that can grow or shrink.

Let's start with a video that clearly demonstrates how to declare and initialize both types.

Array | Solidity 0.8

This video from the Smart Contract Programmer channel provides a concise overview of declaring and initializing fixed-size and dynamic arrays in Solidity.

Watch from the beginning until 02:10. Pay attention to: The syntax for declaring a dynamic array (e.g., uint[]). The syntax for a fixed-size array, where the size is specified (e.g., uint[3]). How to initialize arrays with values when you declare them.

Common Array Operations

Once you've declared an array, you'll need to interact with it. The common operations will feel familiar from your backend development experience, but with some Solidity-specific nuances.

  • Get Element: Access an element by its zero-based index: myArray[2].
  • Update Element: Re-assign the value at a specific index: myArray[2] = newValue;.
  • Get Length: Find out how many elements are in the array using the .length property.
  • Add Element (push): For dynamic storage arrays, you can add an element to the end using .push(value). This increases the array's length by one.
  • Remove Element (pop): For dynamic storage arrays, you can remove the last element using .pop(). This decreases the array's length by one.
  • Delete Element (delete): The delete keyword can be used on an element at a specific index (e.g., delete myArray[2]). Crucially, this does not shorten the array. It simply resets the element at that index to its default value (e.g., 0 for uint, false for bool, address(0) for address). The array's length remains the same.

The next part of the video you started watching covers these operations in detail.

Array | Solidity 0.8

Let's continue with the same video to see these operations in action. The demonstration clearly shows the effect of each operation on a sample array.

Watch from 02:10 to 05:03. Focus on the distinction between how delete and pop affect the array.

The difference between delete and pop is a frequent source of bugs, so it's worth re-emphasizing:

  • myArray.pop(): Removes the last element and reduces myArray.length.
  • delete myArray[i]: Resets the element at index i to its default value and does not change myArray.length.
Test your understanding!

You have a dynamic storage array declared as uint[] public numbers = [10, 20, 30, 40];. What will the array contain and what will its length be after executing the following three lines of code in order?

  1. numbers.push(50);
  2. delete numbers[1];
  3. numbers.pop();
Show answer

Let's trace the state of the numbers array:

  1. Initial state: [10, 20, 30, 40], length = 4
  2. numbers.push(50);: The array becomes [10, 20, 30, 40, 50], length = 5
  3. delete numbers[1];: The element at index 1 (20) is reset to the default value for uint, which is 0. The length does not change. The array is now [10, 0, 30, 40, 50], length = 5.
  4. numbers.pop();: The last element (50) is removed. The length decreases by one.

Final Result: The array will be [10, 0, 30, 40] and its length will be 4.

Data Locations: Storage vs. Memory

This is where your developer background is particularly helpful. In Solidity, you must be explicit about where data lives, as it has major implications for gas costs and behavior. For reference types like arrays, there are three main "data locations":

  • storage: This is the default location for state variables. storage arrays are persistent on the blockchain. Dynamic storage arrays can be resized using push and pop. This is the most expensive location.
  • memory: This is a temporary location used within functions. memory arrays exist only for the duration of a function call. They are much cheaper than storage. A key limitation is that memory arrays cannot be resized after they are created. You must declare their size upfront (e.g., uint[] memory tempArray = new uint[](10);).
  • calldata: This is a special, non-modifiable location for function arguments passed in an external function call. It's the cheapest location because it avoids copying data.

To get the most precise and authoritative information on this, we'll turn to the official Solidity documentation.

Solidity Documentation on Types and Arrays

The official Solidity documentation is the ultimate source of truth. These sections explain the different types of arrays, how their data location affects behavior, and what member functions are available.

Please read the following sections from the documentation: Arrays: Find the heading 'Arrays'. Read this section to understand the syntax for fixed-size, dynamic, and multi-dimensional arrays. Allocating Memory Arrays: Find the subheading 'Allocating Memory Arrays'. This short section explains the new keyword and the immutability of memory array sizes. Array Members: Find the subheading 'Array Members'. Read about the .length, .push(), .push(x), and .pop() members. Pay close attention to the notes on gas costs.

Practical Advice and When to Use Arrays

While arrays are powerful, they must be used with care on the blockchain due to gas costs.

  • Avoid unbounded loops: A for loop that iterates over a large, dynamic array can easily consume more gas than the block gas limit, making your function unusable. If you need to iterate, consider patterns that process data in batches.
  • Avoid returning large arrays: Returning an entire dynamic array from a function copies all its data to memory, which can be very expensive. It's often better to write a function that returns a single element at a given index.

So, when should you use an array vs. a mapping?

  • Use an array when you have a small collection of data or when the order of elements is important. For example, a fixed-size array of three admin addresses.
  • Use a mapping when you need fast lookups by a key and do not need to iterate over the entire collection. For example, mapping(address => uint) public balances;.
  • Combine them! A very common pattern to get the benefits of both is to use a mapping for lookups and a separate array to store the keys, allowing for iteration.
    mapping(address => Investor) public investors;
    address[] public investorKeys;
    
    function addInvestor(...) public {
        // ... add to mapping ...
        investors[newAddress] = ...;
    
        // ... add key to array for iteration ...
        investorKeys.push(newAddress);
    }
    

Conclusion

You've now added arrays, a fundamental data structure, to your Solidity toolkit. You understand the critical differences between fixed-size and dynamic arrays, and the even more important distinction between storage and memory locations.

Key Takeaways:

  • Fixed-size arrays (T[k]) have a constant length, while dynamic arrays (T[]) can grow and shrink.
  • Array operations like .push() and .pop() are only available for dynamic storage arrays.
  • delete myArray[i] resets an element but does not change the array's length.
  • memory arrays are temporary, cheaper, and cannot be resized after creation.
  • Looping over or returning large arrays from functions is very gas-intensive and should be avoided.

Preview of the Next Lesson:
We've now covered two ways to store data collections on-chain: mappings and arrays. But how do we tell the outside world (like your dApp frontend) when this data changes? An off-chain application can't constantly ask "has the balance changed?". The solution is for the contract to announce that something happened. In the next lesson, we will implement events to log important contract activity for off-chain applications. Events are the blockchain's equivalent of a logging framework and are essential for building responsive user interfaces.

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

Sign up