Back to Blog
β€’7 min read

πŸš€ A Practical Guide to Writing Gas-Efficient Solidity Smart Contracts

Learn how to write gas-efficient Solidity smart contracts with practical tips, optimization techniques, and best practices. This guide covers everything from basic optimizations to advanced patterns for reducing gas costs in your decentralized applications.

🧠 Why Gas Optimization Matters

Every operation in Ethereum consumes gas, a unit that represents computational cost. Efficient contracts cost users less to interact with, improve UX, and help your dApp scale. Plus, gas optimization often aligns with better logic and cleaner code.

In this blog, we'll walk through:

  • β€’Common gas inefficiencies and how to fix them
  • β€’Tools like Foundry for gas benchmarking
  • β€’Real Solidity code examples
  • β€’Practical optimization patterns

βš’οΈ Setup: Foundry for Gas Benchmarking

We'll use Foundry, a powerful tool for Ethereum development, to benchmark gas usage. Install it with:

curl -L https://foundry.paradigm.xyz | bash 
foundryup
bash

Create a new project:

forge init GasOptimization
cd GasOptimization
bash

⚠️ Common Gas Pitfalls and Fixes

  • β€’πŸ” Avoid Unbounded Loops
// ❌ Bad
function sum(uint[] memory arr) public pure returns (uint) {
    uint total;
    for (uint i = 0; i < arr.length; i++) {
        total += arr[i];
    }
    return total;
}
solidity

Why it's bad: The loop depends on input size. Gas costs become unbounded.

βœ… Fix: Avoid loops for storage/memory writes. Offload to frontends or batch with limits.

  • β€’πŸ“¦ Use uint256 Over Smaller Types
// ❌ Bad
uint8 a = 1;
uint8 b = 2;
solidity

Why it's bad: EVM reads in 32-byte words. Mixing types causes inefficient packing.

βœ… Fix: Use uint256 everywhere. It’s the EVM’s native word size and avoids packing issues.

uint256 a = 1;
uint256 b = 2;
solidity
  • β€’πŸ’Ύ Pack Storage Variables
// ❌ Unpacked
uint256 a;
bool b;
uint256 c;
solidity

Why it's bad: Each storage slot costs 20,000 gas. Unpacked variables waste space.

βœ… Fix: Pack variables into fewer slots. Use structs or arrays to group related data.

// βœ… Packed
unint256 a;
unint256 b;
bool c;
solidity
  • β€’πŸ—‚οΈ Use calldata for External Function Parameters
// ❌ Memory
function process(uint[] memory data) external {}

// βœ… Calldata
function process(uint[] calldata data) external {}
solidity

calldata is cheaper for external functions since it avoids memory allocation.

  • β€’πŸ§  Short-Circuit Booleans
// ❌ Evaluates both
if (checkA() && checkB()) {}

// βœ… Short-circuits if checkA() is false
if (!checkA()) return;
if (checkB()) {}
solidity

πŸ§ͺ Benchmarking With Foundry

Let’s create a simple test to compare gas.

Example Contract

// SPDX-License-Identifier: MIT
// src/GasExample.sol
pragma solidity ^0.8.24;

contract GasExample {
    // Inefficient
    function sumMemory(uint[] memory arr) external pure returns (uint total) {
        for (uint i = 0; i < arr.length; i++) {
            total += arr[i];
        }
    }

    // Efficient (calldata)
    function sumCalldata(uint[] calldata arr) external pure returns (uint total) {
        for (uint i = 0; i < arr.length; i++) {
            total += arr[i];
        }
    }
}
solidity

Test File

// SPDX-License-Identifier: MIT
// test/GasExample.t.sol
pragma solidity ^0.8.24;

import "forge-std/Test.sol";
import "../src/GasExample.sol";

contract GasExampleTest is Test {
    GasExample gasExample;

    function setUp() public {
        gasExample = new GasExample();
    }

    function testGasSumMemory() public view {
        uint[] memory arr = new uint[](5);
        for (uint i = 0; i < 5; i++) {
            arr[i] = i;
        }
        uint result = gasExample.sumMemory(arr);
        // Expected result: 0 + 1 + 2 + 3 + 4 = 10
        assertEq(result, 10);
    }

    function testGasSumCalldata() public view {
        uint[] memory arr = new uint[](5);
        for (uint i = 0; i < 5; i++) {
            arr[i] = i;
        }
        uint result = gasExample.sumCalldata(arr);
        // Expected result: 0 + 1 + 2 + 3 + 4 = 10
        assertEq(result, 10);
    }
}
solidity

Run Benchmark

forge test --gas-report
bash

You will see gas usage for each function. Compare the results to see the impact of optimizations.

| Function Name        | Max   |
|----------------------|-------|
| testGasSumCalldata   | 2226  |
| testGasSumMemory     | 3699  |

calldata saved us over 1,000 gas compared to memory!

βœ… General Optimization Checklist

  • β€’Use the latest Solidity version for optimizations
  • β€’Avoid unbounded loops and recursion
  • β€’Use calldata for external function parameters
  • β€’Pack storage variables efficiently
  • β€’Short-circuit boolean expressions
  • β€’Minimize state changes in functions
  • β€’Leverage libraries like OpenZeppelin for common patterns

🧠 Final Thoughts

Gas optimization isn't about micro-obsessing β€” it’s about knowing where it matters. If your contract will be called millions of times or touches expensive opcodes, optimize. Otherwise, prioritize readability and security first.

πŸ“š Further Reading

Enjoyed this article?

Follow me for more insights on blockchain development and Web3 innovation.