Back to Blog10 min read
Development

Solidity Gas Optimization: 15 Patterns to Reduce Costs (2026)

Learn the 15 most impactful Solidity gas optimization patterns in 2026. Includes code examples for storage packing, custom errors, unchecked arithmetic, and more. Use the free AI Gas Optimizer to apply all patterns automatically.

August 3, 2026
10 min read
6 views
✓ Written by blockchain developers·✓ Reviewed for technical accuracy·✓ Updated August 2026
Solidity Gas Optimization: 15 Patterns to Reduce Costs (2026)

Solidity Gas Optimization: 15 Patterns to Reduce Costs (2026)

Solidity gas optimization reduces the cost of deploying and running smart contracts on Ethereum. The most impactful patterns include caching storage reads, using custom errors instead of require strings, declaring variables as immutable, packing storage variables, and using unchecked arithmetic blocks. This guide covers 15 proven patterns with before-and-after code examples. Use the free Blockhertz AI Gas Optimizer to apply all optimizations automatically in under 60 seconds.


Why Gas Optimization Matters in 2026

Every operation in a Solidity smart contract costs gas. Every gas unit costs real money. On Ethereum mainnet, a single complex transaction can cost $5 to $50 depending on network conditions. For protocols processing thousands of transactions daily, unoptimized contracts waste hundreds of thousands of dollars per year.

Gas optimization is not premature optimization — it is a professional responsibility. Users pay for every inefficiency you leave in your code.

In 2026, three factors make gas optimization more important than ever:

  • Higher user expectations: Users compare gas costs across competing protocols
  • More competition: Gas-efficient protocols win users from inefficient ones
  • Audit requirements: Security auditors now flag gas inefficiencies alongside vulnerabilities

Understanding Gas Costs

Before optimizing, understand what operations cost the most gas:

Operation Gas Cost Notes
SSTORE (new value) 20,000 Writing new storage slot
SSTORE (update) 2,900 Updating existing slot
SLOAD 800 Reading from storage
MLOAD/MSTORE 3 Reading/writing memory
ADD/SUB/MUL 3-5 Basic arithmetic
External call 2,600+ Calling another contract
Base transaction 21,000 Every transaction minimum

Key insight: Storage operations (SLOAD/SSTORE) are 100-300x more expensive than memory operations. Every optimization strategy targets reducing storage reads and writes.


The 15 Gas Optimization Patterns


Pattern 1: Cache Storage Reads in Memory

Gas saved: ~800 gas per eliminated SLOAD

The most impactful optimization in most contracts. Every time you read from storage costs 800 gas. Reading the same storage variable multiple times in one function is the most common gas waste.

// BEFORE — 3 SLOADs = 2,400 gas
function processBalance() external {
    require(balances[msg.sender] > 0, "No balance");
    emit BalanceRead(balances[msg.sender]);
    totalProcessed += balances[msg.sender];
}

// AFTER — 1 SLOAD = 800 gas
function processBalance() external {
    uint256 balance = balances[msg.sender]; // cache once
    require(balance > 0, "No balance");
    emit BalanceRead(balance);
    totalProcessed += balance;
}

Pattern 2: Use Custom Errors Instead of Require Strings

Gas saved: ~50 gas per revert + smaller bytecode

String error messages in require statements are stored in bytecode and cost gas both at deployment and when the revert is triggered. Custom errors eliminate this cost.

// BEFORE — string stored in bytecode
require(amount > 0, "Amount must be positive");
require(msg.sender == owner, "Not authorized");

// AFTER — no string storage
error AmountMustBePositive();
error NotAuthorized();

if (amount == 0) revert AmountMustBePositive();
if (msg.sender != owner) revert NotAuthorized();

Custom errors also make your error messages ABI-decodable, improving debugging in production.


Pattern 3: Declare Variables as Immutable

Gas saved: ~797 gas per read (immutable = 3 gas vs storage = 800 gas)

Variables set once in the constructor and never changed should be declared immutable. They are stored in contract bytecode rather than storage, making reads nearly free.

// BEFORE — storage read = 800 gas
address public owner;
uint256 public maxSupply;

constructor(address _owner, uint256 _maxSupply) {
    owner = _owner;
    maxSupply = _maxSupply;
}

// AFTER — bytecode read = 3 gas
address public immutable owner;
uint256 public immutable maxSupply;

constructor(address _owner, uint256 _maxSupply) {
    owner = _owner;
    maxSupply = _maxSupply;
}

Pattern 4: Pack Storage Variables

Gas saved: Up to 15,000 gas on deployment, 2,000+ gas per write

Ethereum storage is organized in 32-byte slots. Multiple small variables can share one slot if declared together, reducing the number of storage operations needed.

// BEFORE — 3 storage slots = 3 SSTOREs
uint256 userId;      // slot 0 (32 bytes)
uint256 balance;     // slot 1 (32 bytes)
uint256 timestamp;   // slot 2 (32 bytes)

// AFTER — 1 storage slot = 1 SSTORE
uint128 userId;      // slot 0 (first 16 bytes)
uint64 balance;      // slot 0 (next 8 bytes)
uint64 timestamp;    // slot 0 (last 8 bytes)

Rule: Group variables of smaller types together. Place them consecutively so the compiler can pack them into shared slots.


Pattern 5: Use ++i Instead of i++ in Loops

Gas saved: ~5 gas per iteration

i++ creates a temporary variable holding the old value before incrementing. ++i increments first without the temporary variable. Small per iteration but meaningful in large loops.

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

// AFTER
for (uint256 i; i < array.length; ++i) {
    // loop body
}

// Also: initialize as 0 by default
// uint256 i; is same as uint256 i = 0;
// but saves a tiny amount of gas

Pattern 6: Cache Array Length in Loops

Gas saved: ~100 gas per loop iteration for storage arrays

Accessing array.length in a loop condition reads from storage on every iteration if the array is a storage variable. Cache the length before the loop.

// BEFORE — reads array.length every iteration
for (uint256 i; i < storageArray.length; ++i) {
    process(storageArray[i]);
}

// AFTER — reads length once
uint256 len = storageArray.length;
for (uint256 i; i < len; ++i) {
    process(storageArray[i]);
}

Pattern 7: Use Unchecked Arithmetic Blocks

Gas saved: ~20-30 gas per operation

Solidity 0.8.0+ adds automatic overflow/underflow checks to every arithmetic operation. When you can mathematically prove overflow is impossible, use unchecked to skip these checks.

// BEFORE — checked arithmetic (safe but expensive)
for (uint256 i; i < len; ++i) {
    total += values[i];
}

// AFTER — unchecked (safe because i < len always)
for (uint256 i; i < len; ) {
    total += values[i];
    unchecked { ++i; }
}

// Safe because: i starts at 0, increments by 1,
// and loop exits before i reaches type max

Warning: Only use unchecked when you have manually verified overflow cannot occur. Never use it blindly.


Pattern 8: Use calldata Instead of memory for Read-Only Parameters

Gas saved: ~200-500 gas per function call depending on data size

Function parameters marked memory are copied into memory. Parameters marked calldata are read directly from the call data without copying. Use calldata for any parameter you only read, never modify.

// BEFORE — copies data into memory
function processData(
    bytes memory data,
    uint256[] memory values
) external {
    // read-only operations
}

// AFTER — reads directly from calldata
function processData(
    bytes calldata data,
    uint256[] calldata values
) external {
    // same read-only operations, cheaper
}

Pattern 9: Use external Instead of public

Gas saved: ~10-50 gas per call

public functions can be called both internally and externally. external functions can only be called from outside the contract. External functions are cheaper because they read parameters directly from calldata.

// BEFORE — public (can be called internally)
function transfer(address to, uint256 amount) 
    public {
    // function body
}

// AFTER — external (only external calls)
function transfer(address to, uint256 amount) 
    external {
    // same function body, cheaper for external callers
}

Pattern 10: Short-Circuit Boolean Evaluation

Gas saved: Varies — skip expensive checks when cheap checks fail

Solidity evaluates && and || left to right and stops early when the result is determined. Put cheaper checks first so expensive operations are skipped when possible.

// BEFORE — always runs both checks
require(
    isWhitelisted(msg.sender) && 
    balances[msg.sender] >= amount
);

// AFTER — balance check (cheap) runs first
// isWhitelisted (expensive external call) 
// only runs if balance check passes
require(
    balances[msg.sender] >= amount && 
    isWhitelisted(msg.sender)
);

Pattern 11: Avoid Storing Redundant Data

Gas saved: 20,000 gas per eliminated storage slot

Every new storage slot costs 20,000 gas to write. Never store data that can be computed from other stored data.

// BEFORE — stores total (redundant)
uint256 public total;
mapping(address => uint256) public balances;

function deposit(uint256 amount) external {
    balances[msg.sender] += amount;
    total += amount; // ← unnecessary SSTORE
}

// AFTER — compute total when needed
mapping(address => uint256) public balances;

// Compute total off-chain by reading events
// Or use a subgraph to track it

Pattern 12: Use Events Instead of Storage for Historical Data

Gas saved: ~15,000-18,000 gas per record

Storing historical data on-chain is extremely expensive. Emit events instead — they are stored in transaction logs at a fraction of the cost and are equally accessible to frontends.

// BEFORE — stores full history on-chain
struct Transfer {
    address from;
    address to;
    uint256 amount;
    uint256 timestamp;
}
Transfer[] public transferHistory; // ← very expensive

// AFTER — emit events (same data, 98% cheaper)
event Transfer(
    address indexed from,
    address indexed to,
    uint256 amount,
    uint256 timestamp
);

// Frontend reads events via getLogs()
// The Graph indexes them automatically

Pattern 13: Avoid Zero to Non-Zero Storage Writes

Gas saved: Up to 15,000 gas per write

Writing a non-zero value to a zero storage slot costs 20,000 gas. Writing to an already non-zero slot costs only 2,900 gas. Initialize storage to non-zero values when possible.

// BEFORE — starts at 0, first write costs 20,000
uint256 public counter; // = 0 by default

// AFTER — starts at 1, all writes cost 2,900
uint256 public counter = 1; // non-zero initial value

// Pattern: use (value - 1) in logic to preserve 
// zero-means-unset semantics while avoiding 
// zero-to-nonzero writes

Pattern 14: Batch Operations

Gas saved: 21,000 gas base fee per eliminated transaction

Every transaction on Ethereum costs a minimum of 21,000 gas base fee. Combining multiple operations into one transaction eliminates this overhead cost.

// BEFORE — 3 transactions = 63,000 gas base fee
await contract.approve(spender, amount1);
await contract.approve(spender, amount2);
await contract.approve(spender, amount3);

// AFTER — 1 transaction = 21,000 gas base fee
function batchApprove(
    address[] calldata spenders,
    uint256[] calldata amounts
) external {
    uint256 len = spenders.length;
    for (uint256 i; i < len; ++i) {
        _approve(msg.sender, spenders[i], amounts[i]);
    }
}

Pattern 15: Use Mappings Instead of Arrays for Lookups

Gas saved: O(1) vs O(n) lookup cost

Arrays require iterating to find elements, costing gas proportional to array size. Mappings provide O(1) constant-time lookups regardless of data size.

// BEFORE — O(n) lookup, expensive at scale
address[] public whitelist;

function isWhitelisted(address user) 
    public view returns (bool) {
    for (uint256 i; i < whitelist.length; ++i) {
        if (whitelist[i] == user) return true;
    }
    return false;
}

// AFTER — O(1) lookup, always cheap
mapping(address => bool) public whitelist;

function isWhitelisted(address user) 
    public view returns (bool) {
    return whitelist[user]; // single SLOAD
}

Quick Reference — Gas Savings Summary

Pattern Gas Saved Difficulty
1. Cache storage reads ~800 per SLOAD Easy
2. Custom errors ~50 per revert Easy
3. Immutable variables ~797 per read Easy
4. Storage packing Up to 15,000 Medium
5. ++i over i++ ~5 per iteration Easy
6. Cache array length ~100 per iteration Easy
7. Unchecked arithmetic ~25 per operation Medium
8. calldata over memory ~200-500 per call Easy
9. external over public ~10-50 per call Easy
10. Short-circuit logic Varies Easy
11. No redundant storage 20,000 per slot Medium
12. Events over storage ~18,000 per record Medium
13. Avoid zero writes Up to 15,000 Medium
14. Batch operations 21,000 per tx Medium
15. Mappings over arrays O(n) → O(1) Easy

How to Apply All 15 Patterns Automatically

Reviewing a contract manually for all 15 patterns takes hours. Blockhertz AI Gas Optimizer applies all patterns automatically in under 60 seconds.

How it works:

  1. Go to blockhertz.com/tools/gas-optimizer
  2. Paste your Solidity contract
  3. Click Optimize Gas
  4. Get back a fully optimized contract with every changed line marked // GAS-OPT
  5. See estimated gas savings percentage
  6. Download the optimized .sol file

No signup required for the free tier. No credit card needed.

Try it free: blockhertz.com/tools/gas-optimizer


Manual vs AI Gas Optimization

Manual Review AI Optimizer
Time 2-8 hours Under 60 seconds
Cost $500-2,000 Free
Patterns covered Depends on reviewer All 15 patterns
Output Report only Optimized contract + report
Best for Complex logic review Pattern optimization

Recommended approach:

  1. Run AI Gas Optimizer first — apply all automatic pattern optimizations
  2. Review the GAS-OPT annotations to understand each change
  3. Run your full test suite to verify behavior is unchanged
  4. For complex protocols, follow up with manual review of business logic

Conclusion

Gas optimization is one of the highest-ROI activities in smart contract development. The 15 patterns in this guide are not theoretical — they are battle-tested techniques used by production DeFi protocols handling billions in value.

Start with the easiest wins: cache storage reads, use custom errors, and declare immutable variables. These three patterns alone can reduce gas costs by 20-40% in most contracts with minimal code changes.

For a complete automated analysis of your specific contract, run it through the free Blockhertz AI Gas Optimizer. You'll get a fully optimized version with every change explained — in under 60 seconds.


Want to automatically apply all 15 patterns to your contract? Try the free Blockhertz AI Gas Optimizer — results in under 60 seconds, no signup required.

Views

6

Read Time

10 min read

Likes

0

Published

Aug 3, 2026

gas optimizationsolidityblockhertzblockchainsecurityethereumsolanasmart contractsblockchain developmentweb3blockchain2026web3 securiy 2026
gas optimizationsolidityblockhertzblockchainsecurityethereumsolanasmart contractsblockchain developmentweb3blockchain2026web3 securiy 2026

SIGNAL THREAD

00 SIGNALS

NO SIGNALS YET — BE FIRST TO TRANSMIT

Technical Writer Team Blockhertz

Blockchain & Web3 Innovator

Blockhertz is a collective of blockchain developers, architects, and innovators dedicated to building next-gen Web3 solutions. Our team specialises in DeFi, tokenomics, smart contracts, and distributed systems.