Skip to content
All essays
WebMarch 26, 202412 min

Gas Optimization Strategies

Optimize smart contracts to reduce gas costs

Ü
Ümit Uz
Mobile & Full Stack Developer

Gas optimization is crucial for smart contract development. Optimized contracts save users money, improve UX, and make your dApp more competitive. Let's explore proven strategies to reduce gas consumption.

Understanding Gas

Gas Costs

  • 21,000 gas - Base transaction cost
  • 2,000 gas - Storage slot (sstore)
  • 5,000 gas - Event logging
  • 20 gas - Non-zero value in calldata
  • 4 gas - Zero value in calldata

Gas Price Formula

solidity
Total Cost = Gas Used × Gas Price + Base Fee + Priority Fee

Storage Optimization

Use Calldata Instead of Memory

solidity
// Expensive - memory allocation
function process(string memory data) public {}

// Cheaper - calldata reference
function process(string calldata data) public {}

Pack Variables

solidity
// Wastes storage slot
struct Bad {
    uint128 a;     // 16 bytes
    uint128 b;     // 16 bytes
    uint256 c;     // 32 bytes - new slot
}

// Optimized
struct Good {
    uint256 a;     // 32 bytes
    uint128 b;     // 16 bytes
    uint128 c;     // 16 bytes
}

Use uint256 by Default

solidity
// More expensive
contract Expensive {
    uint8 public counter;  // Uses full 32-byte slot
}

// Cheaper
contract Cheaper {
    uint256 public counter;  // Same slot, more efficient
}

Memory Optimization

Short-Circuit Evaluation

solidity
// Expensive
if (expensiveCheck() || cheapCheck()) {}

// Cheaper
if (cheapCheck() || expensiveCheck()) {}

Batch Operations

solidity
// Expensive - multiple calls
function setValues(uint256[] memory values) public {
    for (uint256 i = 0; i < values.length; i++) {
        values[i] = i;
    }
}

// Cheaper - batch
function setValues(uint256[] calldata values) public {
    require(values.length <= 100);
    // Process in batch
}

Loop Optimization

Avoid Unbounded Loops

solidity
// Dangerous - gas limit risk
function processAll() public {
    for (uint256 i = 0; i < items.length; i++) {
        // process
    }
}

// Safe - limit iterations
function processBatch(uint256 batchSize) public {
    require(batchSize <= 100);
    for (uint256 i = 0; i < batchSize; i++) {
        require(i < items.length);
        // process
    }
}

Cache Array Length

solidity
// Recalculates length each iteration
for (uint256 i = 0; i < array.length; i++) {}

// Caches length
uint256 length = array.length;
for (uint256 i = 0; i < length; i++) {}

Function Optimization

Use Custom Errors

solidity
// Expensive - string storage
error InvalidAmount();

// Cheaper
require(amount > 0, "Amount must be greater than 0");

// Even cheaper
require(amount > 0);

Optimize View Functions

solidity
// Expensive
function getBalance(address user) public view returns (uint256) {
    return balances[user];
}

// Cheaper with caching
mapping(address => uint256) public balances;

Event Logging

Efficient Events

solidity
// Expensive
event Log(string message, uint256 value1, uint256 value2);

// Cheaper
event Log(uint256 indexed value1, uint256 value2);

// Minimal
event Log(uint256 value);

Indexed Parameters

solidity
// Up to 3 indexed parameters
event Transfer(
    address indexed from,
    address indexed to,
    uint256 indexed tokenId,
    uint256 value
);

External Call Optimization

Low-Level Calls

solidity
// Expensive - call creates new memory frame
(bool success, ) = recipient.call{value: amount}("");

// Cheaper - delegatecall
(bool success, ) = recipient.delegatecall(abi.encodeWithSignature("transfer(address,uint256)", to, amount));

Batch External Calls

solidity
// Multicall pattern
function multicall(bytes[] calldata data) external {
    for (uint256 i = 0; i < data.length; i++) {
        (bool success, ) = address(this).delegatecall(data[i]);
        require(success, "Call failed");
    }
}

Design Patterns

Pull Over Push

solidity
// Expensive - multiple transfers
function distribute() public {
    for (uint256 i = 0; i < recipients.length; i++) {
        payable(recipients[i]).transfer(amount);
    }
}

// Cheaper - pull pattern
function claim() public {
    uint256 amount = balances[msg.sender];
    balances[msg.sender] = 0;
    payable(msg.sender).transfer(amount);
}

Lazy Evaluation

solidity
// Calculate only when needed
uint256 private cachedResult;
bool private cached = false;

function expensiveCalculation() public returns (uint256) {
    if (cached) return cachedResult;

    uint256 result = /* complex calculation */;
    cachedResult = result;
    cached = true;
    return result;
}

Assembly Optimization

Yul for Gas Savings

solidity
function yulAdd(uint256 a, uint256 b) public pure returns (uint256) {
    assembly {
        let result := add(a, b)
        mstore(0x00, result)
        return(0x00, 0x20)
    }
}

Memory Operations

solidity
function bytesToBytes32(bytes memory data) public pure returns (bytes32) {
    assembly {
        result := mload(add(data, 32))
    }
}

Compiler Optimizations

Enable Optimizer

javascript
// hardhat.config.js
module.exports = {
    solidity: {
        version: "0.8.0",
        settings: {
            optimizer: {
                enabled: true,
                runs: 200
            }
        }
    }
};

Testing Gas Costs

Hardhat Gas Reporter

javascript
require("hardhat-gas-reporter");

module.exports = {
    gasReporter: {
        enabled: true,
        currency: "USD",
        gasPrice: 20
    }
};

Gas Snapshots

javascript
it("Should match gas snapshot", async function () {
    const tx = await contract.function();
    expect(tx).to.have.gasCost(50000);
});

Common Gas Pitfalls

Storage in Loops

solidity
// VERY EXPENSIVE
for (uint256 i = 0; i < 100; i++) {
    balances[i] = i;  // 100 sstore operations
}

// Much cheaper
uint256[] memory temp = new uint256[](100);
for (uint256 i = 0; i < 100; i++) {
    temp[i] = i;
}
for (uint256 i = 0; i < 100; i++) {
    balances[i] = temp[i];
}

Deleting Large Arrays

solidity
// Expensive
function clear() public {
    delete largeArray;
}

// Cheaper
function clear() public {
    largeArray = new uint256[](0);
}

Conclusion

Gas optimization requires understanding EVM internals and trade-offs. Profile your contracts, measure gas costs, and apply these strategies systematically. Even small optimizations can save significant money at scale.

Enjoyed this essay? Share it.

Related essays

Next essay
React Router Complete Guide