Gas Optimization Secrets: Writing Leaner Solidity in 2026

Gas Optimization Secrets: Writing Leaner Solidity in 2026
Gas fees remain one of the biggest friction points in smart contract development. Whether you're deploying on Ethereum mainnet or an L2, writing lean Solidity isn't optional, it's a competitive advantage. Here's how to do it right.
Why Gas Costs Still Matter in 2026
The proliferation of Layer 2 networks has dramatically reduced the cost of executing transactions, but it hasn't eliminated the incentive to write efficient smart contracts. On Ethereum mainnet, complex contract interactions still cost real money. On L2s, cheap base fees can mask poorly structured contracts that will become expensive at scale. And in competitive DeFi protocols, a contract that consumes 20% less gas than a competitor's isn't just cheaper, it attracts more users and integrators.
Beyond user costs, gas-inefficient contracts are often structurally inefficient: they do more work than necessary, carry more state than they need, and fail in subtle ways under load. Gas optimization is a proxy for code quality.
This guide covers the practical techniques that production-grade Solidity developers use in 2026 to write leaner, cheaper, more reliable contracts.
Understanding the EVM Execution Model
Before optimizing, you need to understand what you're optimizing against. The Ethereum Virtual Machine charges gas for every operation (opcode) executed. Some opcodes are cheap, arithmetic, bitwise operations, stack manipulation. Others are expensive:
SLOAD (reading from storage): 2,100 gas (cold), 100 gas (warm)
SSTORE (writing to storage): 20,000 gas (new value), 2,900 gas (existing value)
CALL: 2,600 gas (cold address)
LOG: 375 gas base + 8 gas per byte of data
The single most impactful thing you can do to reduce gas costs is minimize storage reads and writes. Everything else is secondary.
Storage Optimization: Packing Variables and Avoiding Redundant SSTOREs
Variable Packing
Solidity stores state variables in 32-byte slots. If you declare variables that can share a slot, the compiler will pack them together, but only if you lay them out correctly.
Inefficient (3 storage slots):
uint256 public totalSupply; // slot 0
uint8 public decimals; // slot 1, wastes 31 bytes
address public owner; // slot 2
Optimized (2 storage slots):
uint256 public totalSupply; // slot 0
address public owner; // slot 1, bytes 0–19
uint8 public decimals; // slot 1, byte 20, packed!
This matters most in structs used inside mappings, where each field access can trigger a separate SLOAD.
Avoiding Redundant SSTOREs
Cache storage variables in memory when reading them multiple times within a function:
// Expensive, 3 SLOADs
function bad() external {
require(balances[msg.sender] > 0);
emit Transfer(msg.sender, address(0), balances[msg.sender]);
balances[msg.sender] = 0;
}
// Optimized, 1 SLOAD
function good() external {
uint256 bal = balances[msg.sender];
require(bal > 0);
emit Transfer(msg.sender, address(0), bal);
balances[msg.sender] = 0;
}
Calldata vs Memory vs Storage: Choosing the Right Data Location
For external functions, prefer calldata over memory for read-only reference type parameters. calldata is non-modifiable and avoids copying data into memory.
// More expensive, copies array into memory
function processItems(uint256[] memory items) external { ... }
// Cheaper, reads directly from calldata
function processItems(uint256[] calldata items) external { ... }
For internal computation with complex types, use memory. Never read from storage inside loops, cache the value first.
Using Custom Errors Instead of Revert Strings
Revert strings are stored as bytes in the contract bytecode and cost gas both to deploy and to execute. Custom errors, introduced in Solidity 0.8.4, are significantly cheaper.
// Old way, expensive
require(msg.sender == owner, "Ownable: caller is not the owner");
// New way, cheap
error NotOwner();
if (msg.sender != owner) revert NotOwner();
Custom errors also support parameters, making them more informative without additional cost:
error InsufficientBalance(address account, uint256 available, uint256 required);
if (balance < amount) revert InsufficientBalance(msg.sender, balance, amount);
Loop Optimization and Avoiding Unbounded Arrays
Loops over storage arrays are one of the most common sources of runaway gas costs. A few rules:
Cache array length outside the loop:
// Reads .length from storage on every iteration
for (uint i = 0; i < myArray.length; i++) { ... }
// Reads length once
uint256 len = myArray.length;
for (uint i = 0; i < len; i++) { ... }
Use ++i instead of i++, the pre-increment avoids storing a temporary value.
Avoid unbounded loops entirely in state-changing functions. If you must iterate over a dynamic array, cap the iteration count or use pagination patterns. An array that grows without bounds is a denial-of-service vector waiting to happen.
Compiler Settings: Optimizer Runs and the via-IR Pipeline
The Solidity compiler's optimizer can dramatically reduce bytecode size and execution cost. Two settings matter most:
Optimizer Runs
The runs parameter tells the optimizer to optimize for a contract that will be called n times. Higher values optimize for runtime efficiency (cheaper calls); lower values optimize for deployment cost (smaller bytecode).
// foundry.toml
[profile.default]
optimizer = true
optimizer_runs = 200 // balanced default
For frequently called contracts (AMMs, lending protocols), try optimizer_runs = 1000 or higher.
via-IR Pipeline
The intermediate representation (IR) pipeline (via_ir = true) enables more aggressive optimizations across function boundaries. It increases compile time but can yield 10–30% gas reductions on complex contracts.
via_ir = true
Test carefully, via-IR can occasionally change behavior in edge cases.
Tools: Gas Profilers and Snapshot Testing with Foundry
Gas Snapshots
Foundry's forge snapshot command generates a .gas-snapshot file that records gas usage for every test. Commit this file to your repo and use it as a regression guard:
forge snapshot
# later...
forge snapshot, diff # shows gas changes since last snapshot
Forge Gas Reports
forge test, gas-report
This outputs a table of min/avg/max gas per function call, essential for identifying hotspots before they hit production.
forge-std's vm.startSnapshotGas
For surgical measurement of specific code paths:
vm.startSnapshotGas("myOperation");
contract.doThing();
uint256 gasUsed = vm.stopSnapshotGas();
Real Before/After Examples With Measurable Savings
Example: ERC-20 Transfer Function
A naive transfer implementation using string reverts, unoptimized storage reads, and no packing used ~51,000 gas in benchmarks. After applying custom errors, storage caching, and variable packing, the same operation dropped to ~34,000 gas, a 33% reduction.
Example: Batch Airdrop
An unbounded loop airdropping tokens to 500 addresses exceeded the block gas limit entirely. Replacing it with a capped batch function (50 addresses per call) with calldata arrays and cached balances reduced per-address cost by 41%.
These aren't edge cases, they're patterns that appear in production contracts every week.
Key Takeaways
- Storage operations (SSTORE/SLOAD) dominate gas costs; pack variables into single 32-byte slots and minimize redundant writes.
- Use calldata instead of memory for read-only function parameters to save gas on external calls.
- Replace revert strings with custom errors to reduce both deployment and runtime gas costs.
- Enable the Solidity optimizer with appropriate runs settings and consider the via-IR pipeline for complex contracts.
- Profile gas usage with Foundry's gas snapshots and treat optimization as a continuous practice, not a one-time task.
Conclusion: Making Gas Optimization a Habit, Not an Afterthought
The developers who ship the most gas-efficient contracts aren't running one-off optimization sprints. They build the habits into their workflow: they profile during development with forge snapshot, they use custom errors from day one, they think about storage layout before writing their first state variable.
Gas optimization and code quality reinforce each other. A contract that wastes gas is usually doing something it doesn't need to do.
If you're preparing to deploy a smart contract, Autheo gives you the deployment infrastructure and environment management to move from local optimization to mainnet launch without losing track of what you've built. Start building leaner, and launch with confidence.
Gear Up with Autheo
Rep the network. Official merch from the Autheo Store.
Theo Nova
The editorial voice of Autheo
Research-driven coverage of Layer-0 infrastructure, decentralized AI, and the integration era of Web3. Written and reviewed by the Autheo content and engineering teams.
About this author →Get the Autheo Daily
Blockchain insights, AI trends, and Web3 infrastructure updates delivered to your inbox every morning.



