Published on

May 8, 2024

Solidity Gas Savings: Exploring the Swap and Pop Array Approach

The Goal

When facing a large array and the need to efficiently remove a value from it while avoiding excessive gas costs, the "swap and pop” pattern becomes invaluable.

TLDR;

The "swap and pop” pattern involves swapping the value at a particular index with the last element in the array and then popping the last element off the array. This operation is commonly used to efficiently remove an element from an array without needing to shift all subsequent elements. It's crucial to recognize that this pattern is suitable only for scenarios where the order of elements is not critical.

Acknowledgment

The initial example was provided from a Calyptus Solidity challenge. They've made valuable contributions to the Ethereum community, and their insights are appreciated.

Example Scenario

Imagine you're working with a smart contract, GalacticGarbageCollector, which manages an array called cosmicArray. Each element in this array represents cosmic data nodes. The contract needs a method, purgeNull, to remove a value from cosmicArray by index. Let's explore the implications of different approaches.

contract GalacticGarbageCollector {
    uint256[] public cosmicArray = [1, 2, 3, 4, 5];

    function purgeNull(uint256 anomalyIndex) external {
        require(
            anomalyIndex < cosmicArray.length,
            "Anomaly beyond celestial bounds"
        );
        delete cosmicArray[anomalyIndex];
    }

    function inspectCosmos() public view returns (uint256) {
        return cosmicArray.length;
    }
}

Humm, there's a subtle issue here. The delete keyword doesn't shrink the array length. Instead, it sets the value at the specified index to its default value (zero for uint). To truly reduce the array length, we need a different approach.

The Problem with Shifting Elements

One might think of shifting elements after deleting the specified index. But this approach, while seemingly intuitive, is gas-inefficient and can lead to gas limits, especially for large arrays.

contract GalacticGarbageCollector {
    uint256[] public cosmicArray = [1, 2, 3, 4, 5];

    function purgeNull(uint256 anomalyIndex) external {
        require(
            anomalyIndex < cosmicArray.length,
            "Anomaly beyond celestial bounds"
        );

        // Shift all elements after the deleted one
        for (uint256 i = anomalyIndex; i < cosmicArray.length - 1; i++) {
            cosmicArray[i] = cosmicArray[i + 1];
        }

        // Remove the last element (now a duplicate of the second to last)
        cosmicArray.pop();
    }

    function inspectCosmos() public view returns (uint256) {
        return cosmicArray.length;
    }
}

The Efficient Solution: Swap and Pop

Instead, let's leverage the "swap and pop" pattern:

contract GalacticGarbageCollector {
    uint256[] public cosmicArray = [1, 2, 3, 4, 5];

    function purgeNull(uint256 anomalyIndex) external {
        require(
            anomalyIndex < cosmicArray.length,
            "Anomaly beyond celestial bounds"
        );

        // Swap with the last element
        cosmicArray[anomalyIndex] = cosmicArray[cosmicArray.length - 1];

        // Pop the last element
        cosmicArray.pop();
    }

    function inspectCosmos() public view returns (uint256) {
        return cosmicArray.length; // Returning the true
    }
}

By swapping the value at the specified index with the last element and then popping the last element, we efficiently remove the value from the array without shifting all subsequent elements. This pattern optimizes gas usage, especially for large arrays, making it the preferred choice in scenarios where element order isn't critical.

That’s it

By keeping it short, concise, and informative, I've just equipped yourself with another valuable tool for your developer toolbox. Keep optimizing, keep learning, and keep building amazing things!

This post was updated on

June 20, 2024.