Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
LABS
Guides

How to Reduce Unintended Gas Consumption

A developer guide to identifying and eliminating hidden gas costs in EVM smart contracts through storage, memory, and execution layer optimizations.
Chainscore © 2026
introduction
GUIDE

Introduction to Gas Optimization

Learn how to identify and eliminate common sources of wasted gas in your smart contracts to reduce transaction costs and improve network efficiency.

Gas optimization is the practice of writing smart contracts that execute transactions using the minimal amount of computational resources, measured in gas units, on the Ethereum Virtual Machine (EVM). Every operation, from storage writes to arithmetic calculations, has a fixed gas cost defined in the Ethereum Yellow Paper. High gas consumption directly translates to higher transaction fees for users and can render a dApp economically unviable. The goal is not just to write working code, but to write efficient code that respects the user's cost constraints and the network's limited block space.

Unintended gas consumption often stems from inefficient storage patterns and expensive operations within loops. A primary culprit is unnecessary use of the SSTORE opcode, which writes data to contract storage—one of the most expensive operations on the EVM. For example, writing a non-zero value to a previously zero storage slot costs 20,000 gas, while updating an existing non-zero value costs 5,000 gas. Similarly, reading from storage (SLOAD) costs 2,100 gas, compared to just 3 gas for a memory read. A common anti-pattern is reading the same storage variable multiple times within a function instead of caching it in a local memory variable.

Consider a function that checks and updates a user's balance in a loop. The unoptimized version might repeatedly call balances[user] inside the loop, incurring multiple SLOAD costs. The optimized version would cache the balance in a local uint256 variable at the start, perform all calculations in memory, and write the final result back to storage once. This simple change can reduce gas costs by thousands of units per iteration. Other costly operations include CALL (to external contracts), SHA3 (for hashing), and operations that expand memory size.

Beyond storage, function visibility and data types impact gas. public variables implicitly generate getter functions, and marking a function as public when it is only called internally forces the EVM to handle unnecessary input validation and copying. Using uint256 for all integers is generally the most gas-efficient, as the EVM operates on 256-bit words; using smaller types like uint8 can actually increase gas costs due to the extra operations needed for masking and conversion. Packing multiple small variables into a single storage slot using structs is an advanced technique that can yield significant savings.

Effective optimization requires profiling. Tools like Hardhat and Foundry include gas reporters that show the cost of each function call in your tests. The Remix IDE also has a built-in gas profiler. Use these to establish a baseline, apply optimizations like storage caching and loop refinements, and then re-measure. Remember the principle of diminishing returns: focus first on high-frequency transactions and operations inside loops. The ultimate resource is the EVM's own opcode gas costs, which provide the foundational costs for every operation your contract performs.

prerequisites
PREREQUISITES AND TOOLS

How to Reduce Unintended Gas Consumption

Optimizing gas usage is a critical skill for Ethereum developers. This guide covers the essential tools and foundational knowledge needed to identify and eliminate wasteful gas spending in your smart contracts.

Before you can optimize, you need to measure. The primary tool for this is a gas profiler. Hardhat and Foundry, the leading Ethereum development frameworks, both include built-in profilers. Running forge test --gas-report in Foundry or using Hardhat's gas reporter plugin will show you a detailed breakdown of gas costs per function call in your tests. This is your first step in identifying gas hotspots—the most expensive operations in your contract, such as storage writes, loop iterations, or complex computations.

Understanding EVM opcode costs is fundamental to interpreting these reports. The Ethereum Yellowpaper defines gas costs for each operation. High-cost operations include SSTORE for writing to storage (up to 20,100 gas for a new value), SLOAD for reading storage (2,100 gas), and contract creation via CREATE (32,000 gas). In contrast, operations in memory or stack are cheap. Tools like the EVM Codes reference are invaluable for looking up specific opcode costs and understanding the underlying mechanics of your Solidity code.

Your local development environment must accurately simulate mainnet conditions. Configure your Hardhat or Foundry network to use the latest London hardfork parameters, including the current base fee. This ensures your gas estimates reflect real-world conditions, especially the impact of EIP-1559. Always test optimization changes against a comprehensive suite of unit tests to ensure functionality is preserved. A single bug introduced during optimization can cost far more than the gas you save.

key-concepts-text
GAS OPTIMIZATION

Key Gas Concepts: Storage, Memory, and Computation

Understanding how Ethereum's gas costs are allocated across storage, memory, and computation is the first step to writing efficient, cost-effective smart contracts.

Every Ethereum transaction consumes gas, a unit of computational effort. The total gas cost is the sum of three primary components: storage operations, memory usage, and computational steps. Each component has distinct pricing, with storage being the most expensive by a significant margin. Optimizing your contract requires knowing which operations trigger which costs. For example, writing a new value to a state variable (storage) costs 20,000 gas, while adding two numbers in a function (computation) might cost just 3 gas.

Storage refers to data persisted on the blockchain between transactions. It is the most expensive resource because it increases the global state size that all nodes must store forever. Key costs include: SSTORE for a new value (20,000 gas), SSTORE for changing an existing non-zero value (5,000 gas), and SLOAD for reading (800 gas). A common optimization is to minimize the number of state variables, pack multiple small uints into a single slot using bit-packing, and use events for data that doesn't need on-chain access.

Memory is a temporary, expandable byte array used during a contract's execution. It is cheaper than storage but more expensive than stack operations. Memory costs scale quadratically: the first 724 bytes are linear, but expanding it further incurs higher costs. Using the memory keyword for function arguments and local variables is standard, but be cautious with operations that copy large arrays in memory, as each byte copied costs gas. Reusing memory locations and limiting array sizes are effective strategies.

Computation covers the cost of executing EVM opcodes, from arithmetic (ADD, MUL) to cryptographic functions (SHA3, ECRECOVER). While generally cheap, costs accumulate in complex logic loops. Use uint256 for math, as other types require extra conversion opcodes. Avoid expensive operations in loops, and use short-circuiting in conditionals (e.g., require(conditionA && conditionB)) to prevent evaluating the second condition if the first fails. Pre-calculating values off-chain and passing them as parameters can also reduce on-chain computation.

To systematically reduce gas, profile your contract using tools like Hardhat Gas Reporter or Ethereum's execution traces. Focus on the high-impact areas: convert storage to memory or calldata where possible, use fixed-size arrays, and leverage libraries for repeated logic. Remember that gas savings compound; a 10-gas optimization in a function called 10,000 times saves 100,000 gas. Always test optimizations on a testnet to verify correctness, as some gas-saving techniques can introduce subtle bugs.

common-pitfalls
OPTIMIZATION GUIDE

Common Sources of Unintended Gas Costs

Gas inefficiencies often stem from predictable patterns in smart contract development. Identifying and mitigating these common sources can lead to significant transaction cost savings.

01

Excessive Storage Operations

Writing to and reading from contract storage are among the most expensive EVM operations. Common pitfalls include:

  • Repeated SLOAD/SSTORE in loops.
  • Storing data that could be computed or passed as function parameters.
  • Using overly complex structs that increase storage slot usage.

Optimize by caching storage variables in memory, using packed variables, and minimizing state changes.

02

Inefficient Data Structures & Loops

Unbounded loops that iterate over dynamically-sized arrays can cause gas costs to explode, potentially leading to out-of-gas errors. This is critical for functions that are part of user transactions.

Mitigation strategies:

  • Use mappings instead of arrays for lookups.
  • Implement pagination or limit loop iterations.
  • Shift aggregation logic off-chain when possible.
03

Redundant External Calls

Every external call to another contract (CALL, STATICCALL, DELEGATECALL) incurs significant gas. Unoptimized patterns include:

  • Making the same call multiple times instead of caching the result.
  • Calling external contracts within loops.
  • Not using view/pure functions for read-only operations.

Batch calls and minimize cross-contract interaction within transaction execution.

04

Suboptimal Variable Packing

The EVM reads and writes storage in 32-byte slots. Inefficient use of these slots wastes gas.

  • Example: Declaring multiple uint128 variables separately uses multiple slots. Packing them into a single slot can cut storage costs by 50% or more.
  • Use uint8, uint16, etc., strategically within structs to enable automatic packing by the compiler.
05

Inheritance & Constructor Gas

Deep inheritance chains and complex constructors can inflate deployment and initialization costs. Each parent constructor call adds bytecode and execution overhead.

Key considerations:

  • Flatten inheritance hierarchies where possible.
  • Avoid expensive operations or external calls in constructors.
  • Use proxy patterns (like Transparent or UUPS) for expensive logic contracts, deploying them only once.
06

Event Emission Overhead

While cheaper than storage, emitting events with large or numerous indexed (indexed) parameters increases gas cost. Each indexed parameter costs ~2000 gas and creates a topic in the log.

Best practices:

  • Only mark parameters as indexed if they need to be filterable in off-chain queries.
  • Limit the total data emitted in event arguments.
  • Use non-indexed parameters for large data blobs.
ETHEREUM MAINNET

Gas Cost Comparison: Inefficient vs. Optimized Patterns

Gas costs in gwei for common operations, comparing naive implementations against optimized alternatives.

OperationInefficient PatternOptimized PatternGas Saved

Loop Storage Reads

21,000 gas per iteration

Cache variable outside loop

~5,000 gas per read

State Variable Updates

20,000 gas (cold slot)

5,000 gas (warm slot)

15,000 gas

External Call Gas Limit

Unspecified (default)

Specified with gasleft() - 1000

Prevents out-of-gas reverts

Boolean Packing

Separate bool storage variables

Packed into single uint256

~20,000 gas per variable

Constant vs. Immutable

Storage constant value

immutable or compile-time constant

~20,000 gas read vs. ~100 gas

Function Visibility

public (external input checks)

external for public functions

~200 gas per call

Redundant Checks

Multiple require statements

Single combined condition

~500 gas per redundant opcode

storage-optimization
GAS OPTIMIZATION

Step 1: Optimize Storage Layout and Operations

Smart contract gas costs are dominated by storage. This guide explains how to structure your data and manage state changes to minimize unintended consumption.

Every storage slot in the EVM costs approximately 20,000 gas for a first-time write (SSTORE) and 2,900 gas for subsequent updates. Reading from storage (SLOAD) costs 100 gas. These are the most expensive operations in your contract. The key to optimization is to minimize the total number of storage slots used and to batch operations to reduce the frequency of writes. Poorly packed data structures can waste slots and multiply these base costs unnecessarily.

Solidity stores variables in 256-bit (32-byte) slots. You can pack multiple smaller variables into a single slot if they are declared consecutively. For example, a uint128 and a uint128 can share one slot, but a uint256 between them would break the packing. Use uint8, uint32, uint64, and uint128 strategically. Consider this optimized struct:

solidity
struct User {
    uint64 lastInteraction; // Uses first 8 bytes of slot 0
    uint96 balance;         // Uses next 12 bytes of slot 0
    address wallet;         // Uses final 20 bytes of slot 0
    // Total: 40 bytes packed into one 32-byte slot? No! Address forces a new slot.
}

The address starts a new slot because it doesn't fit in the remaining 12 bytes after the uint96. Reordering to address wallet; uint64 lastInteraction; uint96 balance; packs all three into one slot.

Beyond packing, consider your storage access patterns. Using a mapping is often cheaper than an array for lookups, as it avoids iteration. However, beware of the gas cost of expanding a mapping versus an array. For state variables that change together, group them into a single struct that is updated in one transaction to pay the storage update cost once. Use immutable and constant variables for values that are set at deployment or compile time, as they are stored in the contract bytecode, not in expensive storage slots.

Another critical tactic is to minimize storage writes inside loops. Writing to storage on every iteration is extremely costly. Instead, perform calculations in memory and write the final result back to storage once. Furthermore, consider using SSTORE opcode's behavior: writing a non-zero value to a zero-value slot costs 22,100 gas, but changing a non-zero value to zero refunds 4,800 gas. Designing state changes that leverage these refunds, where safe, can reduce net gas costs.

Finally, use events to store historical data instead of on-chain storage when the data is only needed for off-chain indexing. Events are significantly cheaper (approximately 375 gas per byte of logged data) and are a core mechanism for the EVM log system. Tools like The Graph can then index this emitted data. Reserve contract storage only for state that is essential for the contract's on-chain logic and future execution.

memory-calldata-optimization
GAS OPTIMIZATION

Step 2: Efficient Use of Memory, Calldata, and Stack

Understanding Ethereum's data storage locations is fundamental to writing gas-efficient smart contracts. This guide explains the costs and best practices for using memory, calldata, and the stack.

Every operation in the Ethereum Virtual Machine (EVM) consumes gas, and data storage is a primary cost driver. The three key temporary data locations are memory (mutable, paid per word), calldata (immutable, read-only input), and the stack (limited, free but volatile). Using the wrong location for a given task can lead to significant, unintended gas consumption. For example, copying large arrays from calldata to memory is expensive and often unnecessary.

Calldata is the cheapest for external function inputs. When a function is marked external, its parameters are stored in calldata. This is a non-modifiable, persistent area where reading is very low-cost. You should default to using calldata for array and struct parameters you only need to read. For instance, a function that verifies signatures in a list should accept the list as calldata, not memory.

solidity
// Gas-efficient: Uses calldata for read-only input
function verifySignatures(
    bytes[] calldata signatures,
    address[] calldata signers
) external { ... }

Memory should be used strategically for mutable data. Allocating memory has a cost, and expanding it (e.g., via push on a dynamic array) is quadratic. Use memory for data you need to modify within the function or return to the caller. A common optimization is to cache a state variable in a local memory variable if it's accessed multiple times, as SLOAD operations (reading storage) are far more expensive than MLOAD (reading memory).

The stack is for local variables and computation. It holds up to 1024 items of 32 bytes each and is the fastest, gas-free location. Simple value types like uint256, bool, and address are stored here by default. However, you cannot reference the stack from other execution contexts, and complex types like arrays or structs cannot reside there. Exceeding the stack limit (stack too deep error) is a sign your function should be refactored.

To minimize gas, follow these actionable rules: 1) Use calldata for all external function inputs you don't need to modify. 2) Use memory only for necessary mutable operations or for returning complex types. 3) Avoid unnecessary copying between data locations. 4) Cache frequently accessed storage variables in stack or memory variables. Applying these principles can reduce function gas costs by thousands of units, especially for functions handling large data sets.

execution-logic-optimization
GAS OPTIMIZATION

Step 3: Optimize Contract Logic and External Calls

Unnecessary gas consumption often stems from inefficient contract logic and expensive external interactions. This step focuses on refactoring your code to minimize computational overhead.

The most significant gas savings come from algorithmic optimization. Review loops, especially those iterating over unbounded arrays stored on-chain. A common pattern is using a for loop to process user arrays, which can become prohibitively expensive as the array grows. Consider using mappings for lookups, processing data off-chain and submitting a proof, or implementing pagination. For example, instead of for(uint i; i < users.length; i++) { process(users[i]); }, store user status in a mapping and allow users to claim rewards individually via a claim() function.

External calls to other contracts are among the most expensive EVM operations. Each call(), delegatecall(), or staticcall() costs at least 100 gas plus the cost of the callee's execution. Batch these calls where possible. If your contract must check multiple oracle prices, use a single oracle that provides a data structure (like an array of prices) instead of making separate calls for each asset. Furthermore, always check the return value of low-level calls to avoid silent failures that still consume gas.

State variable access patterns are critical. Reading from storage (sload) costs 2100 gas for a cold slot, while writing (sstore) can cost 20,000+ gas. Optimize by using immutable and constant variables for values that do not change, as they are embedded directly in the bytecode. Group related state variables that are accessed together within the same function to leverage the warm storage access discount (100 gas after the first sload).

Be strategic with function modifiers and checks. Require statements that involve complex logic or storage reads execute every time the function is called. Move invariant checks or pre-calculations into an initializer or constructor if they only need to be computed once. For example, instead of calculating a derived address in every function that needs it, store it as an immutable variable upon deployment.

Finally, leverage Solidity's built-in gas optimizations. Use unchecked blocks for arithmetic where overflow/underflow is impossible, such as in loop increments after ensuring bounds (for (uint i; i < length;) { ... unchecked { ++i; } }). Use calldata instead of memory for array function parameters you only need to read. These small changes, when applied across a high-traffic contract, can lead to substantial gas savings for all users.

TROUBLESHOOTING

Frequently Asked Questions on Gas Optimization

Common developer questions about unexpected gas costs, transaction failures, and optimization techniques for EVM-based smart contracts.

This typically indicates a revert in your contract logic, not an actual gas shortage. The EVM consumes all gas up to the point of failure. Common causes include:

  • Failed require/assert statements: A condition like require(balance > amount, "Insufficient funds"); fails.
  • External call failures: A low-level call() to another contract that reverts.
  • State mismatch: Logic depends on a state variable that changed since you constructed the transaction.

Debugging steps:

  1. Check the revert reason using a local fork (Hardhat, Foundry) with tracing enabled.
  2. Use Tenderly or OpenChain to simulate the transaction and see the exact opcode where it fails.
  3. Verify all pre-conditions and dependencies are met at the block where the transaction will execute.
conclusion
KEY TAKEAWAYS

Conclusion and Next Steps

Optimizing gas consumption is a continuous process that directly impacts user experience and protocol sustainability. This guide has outlined core strategies for identifying and mitigating unintended gas waste.

Effective gas optimization requires a multi-layered approach. Start by profiling your contracts with tools like Hardhat Gas Reporter or Foundry's forge snapshot to establish a baseline. Implement the core technical strategies discussed: using fixed-size bytes32 for storage, leveraging calldata for function parameters, minimizing on-chain computations, and batching operations. Remember that the most expensive gas costs often come from storage operations (SSTORE) and contract deployment, so architect your data structures and upgrade paths with these in mind.

Your optimization work doesn't end at deployment. Continuous monitoring is essential. Integrate gas tracking into your CI/CD pipeline using services like Tenderly or OpenZeppelin Defender to catch regressions. For live contracts, analyze real user transaction patterns on block explorers; you may discover common paths that are unnecessarily costly. Consider implementing gas refund mechanisms or meta-transactions via relayers for specific user actions to abstract cost complexity away from end-users.

The next step is to explore advanced patterns and stay current with Ethereum upgrades. Study gas-efficient standards like ERC-4337 for account abstraction and ERC-1155 for multi-token contracts. With the advent of EIP-4844 (proto-danksharding) and future scaling solutions, new optimization avenues for calldata and storage are emerging. Always audit optimizer assumptions; the Solidity compiler's optimizer can behave differently between versions, and inline assembly requires rigorous testing. Finally, contribute to and learn from the community by reviewing gas reports on major protocol upgrades and participating in forums like the Ethereum Magicians.

How to Reduce Unintended Gas Consumption in Smart Contracts | ChainScore Guides