
How to Gas Optimize a Smart Contract: Complete Checklist (2026)
Every unnecessary computation in your smart contract costs your users real money. On Ethereum mainnet in 2026, a single poorly optimized transaction can cost $15–$40 in gas fees. Multiply that across thousands of users and you have a protocol that bleeds adoption. This checklist covers the 15 most impactful Solidity gas optimization patterns — with before/after code examples, measurable savings, and how to automate the process with AI tooling.
This post is part of the Blockhertz Smart Contract Security Checklist series. If you haven't read the audit checklist yet, start with How to Audit a Smart Contract: Complete Security Checklist (2026).
Why Gas Optimization Is a Security Issue
Gas optimization is not just a cost concern — it is a security concern. Contracts that consume excessive gas are vulnerable to denial-of-service attacks where attackers force functions to hit the block gas limit. Unbounded loops, inefficient storage reads, and redundant computations all create attack surfaces. Optimizing gas closes those vectors while reducing costs for every user.
- Block gas limit DoS — loops that can be made to run indefinitely
- Out-of-gas reverts — legitimate transactions failing under load
- Front-running amplification — expensive transactions stay in mempool longer
The 15-Pattern Gas Optimization Checklist
1. Use uint256 Instead of Smaller Integer Types
The EVM operates on 32-byte slots. Using uint8 or uint128 does not save gas unless you are packing multiple variables into one storage slot. Outside of struct packing, smaller types cost more due to additional masking operations.
// ❌ More expensive — EVM still uses 32 bytes, adds masking
uint8 public counter;
// ✅ Cheaper for standalone variables
uint256 public counter;
Gas saved: 20–200 gas per operation depending on frequency.
2. Pack Struct Variables to Minimize Storage Slots
Each storage slot is 32 bytes. Packing smaller types together into one slot reduces the number of SSTORE/SLOAD operations — the most expensive EVM opcodes.
// ❌ Uses 3 storage slots
struct Unpacked {
uint256 a; // slot 0
uint128 b; // slot 1
uint128 c; // slot 2
}
// ✅ Uses 2 storage slots — b and c share slot 1
struct Packed {
uint256 a; // slot 0
uint128 b; // slot 1 (first 16 bytes)
uint128 c; // slot 1 (last 16 bytes)
}
Gas saved: 20,000 gas per avoided SSTORE on first write.
3. Use calldata Instead of memory for Read-Only Parameters
Function parameters marked memory are copied from calldata into memory — an unnecessary operation when you only read them.
// ❌ Copies array into memory
function processItems(uint256[] memory items) external {
// ✅ Reads directly from calldata
function processItems(uint256[] calldata items) external {
Gas saved: 100–1,000+ gas depending on array size.
4. Cache Storage Variables in Local Variables
Every SLOAD costs 100 gas (warm) or 2,100 gas (cold). If you read a storage variable more than once in a function, cache it in a local variable first.
// ❌ Two SLOADs
function double() external view returns (uint256) {
return value + value;
}
// ✅ One SLOAD, one MLOAD
function double() external view returns (uint256) {
uint256 _value = value;
return _value + _value;
}
Gas saved: 100–2,000 gas per repeated read.
5. Use ++i Instead of i++ in Loops
Post-increment (i++) creates a temporary copy of the variable. Pre-increment (++i) does not.
// ❌ Creates temporary variable
for (uint256 i = 0; i < length; i++) {
// ✅ No temporary variable
for (uint256 i = 0; i < length; ++i) {
Gas saved: ~5 gas per iteration. Significant in large loops.
6. Cache Array Length Outside Loops
Reading array.length inside a loop condition re-reads from storage or memory on every iteration.
// ❌ Reads length every iteration
for (uint256 i = 0; i < items.length; ++i) {
// ✅ Reads length once
uint256 length = items.length;
for (uint256 i = 0; i < length; ++i) {
Gas saved: 3–100 gas per iteration depending on storage vs memory array.
7. Use Custom Errors Instead of Revert Strings
Custom errors were introduced in Solidity 0.8.4. They are significantly cheaper than string revert messages because they encode as a 4-byte selector instead of a full string.
// ❌ String stored in bytecode — expensive
require(msg.sender == owner, "Not the owner");
// ✅ 4-byte selector only
error NotOwner();
if (msg.sender != owner) revert NotOwner();
Gas saved: 50–150 gas per revert. Reduces deployment cost too.
8. Use immutable for Variables Set Once at Construction
Variables set in the constructor and never changed should be immutable. They are stored in bytecode rather than storage — zero SLOAD cost.
// ❌ Storage read on every access
address public owner;
// ✅ Bytecode — no SLOAD
address public immutable owner;
constructor(address _owner) {
owner = _owner;
}
Gas saved: 2,100 gas (cold SLOAD) on every read.
9. Use constant for Fixed Values
Like immutable, constant values are embedded in bytecode. Use for values known at compile time.
// ❌ Storage slot wasted
uint256 public MAX_SUPPLY = 10000;
// ✅ No storage slot
uint256 public constant MAX_SUPPLY = 10000;
Gas saved: 2,100 gas per read + saves one storage slot.
10. Short-Circuit Boolean Conditions
Place the cheapest or most likely to fail condition first in && and || expressions. Solidity evaluates left to right and stops early.
// ❌ Expensive check runs first even if cheap check would fail
if (expensiveCheck() && cheapCheck()) {
// ✅ Cheap check runs first — fails fast
if (cheapCheck() && expensiveCheck()) {
Gas saved: Varies — can avoid hundreds of gas when cheap condition fails.
11. Avoid Redundant Zero Initialization
In Solidity, variables default to zero. Explicitly initializing them to zero wastes gas.
// ❌ Redundant — uint256 defaults to 0
uint256 public counter = 0;
// ✅ Cleaner and cheaper
uint256 public counter;
Gas saved: Small but consistent across all variable declarations.
12. Use Mappings Instead of Arrays Where Possible
Searching arrays requires looping — O(n) gas. Mappings provide O(1) lookup. For balance tracking, allowances, and ownership — always use mappings.
// ❌ O(n) lookup
address[] public holders;
// ✅ O(1) lookup
mapping(address => uint256) public balances;
Gas saved: Dramatic for large datasets — can reduce thousands of gas to hundreds.
13. Use Events Instead of Storage for Historical Data
Emitting an event costs ~375 gas per topic. Writing to storage costs 20,000 gas (first write) or 5,000 gas (update). If you only need historical data off-chain, emit an event instead of storing it.
// ❌ Stores full history on-chain — very expensive
uint256[] public transactionHistory;
// ✅ Off-chain indexable, fraction of the cost
event TransactionRecorded(address indexed user, uint256 amount);
emit TransactionRecorded(msg.sender, amount);
Gas saved: 19,000+ gas per record for first writes.
14. Batch Operations Where Possible
Every transaction has a 21,000 gas base cost. Batching multiple operations into one transaction amortizes that base cost across all operations.
// ❌ 3 transactions × 21,000 base = 63,000 base gas
transfer(alice, 100);
transfer(bob, 200);
transfer(carol, 300);
// ✅ 1 transaction × 21,000 base = 21,000 base gas
function batchTransfer(
address[] calldata recipients,
uint256[] calldata amounts
) external {
for (uint256 i = 0; i < recipients.length; ++i) {
_transfer(recipients[i], amounts[i]);
}
}
Gas saved: 42,000 base gas for 3 operations, scales linearly.
15. Use the Optimizer and Set Runs Appropriately
The Solidity compiler optimizer trades deployment cost against runtime cost. Set runs to reflect how often your contract will be called:
- Low runs (200): Cheaper to deploy, more expensive to call — use for contracts deployed once but called rarely
- High runs (10000+): More expensive to deploy, cheaper to call — use for contracts called thousands of times (DEXes, lending protocols)
// hardhat.config.js
solidity: {
version: "0.8.26",
settings: {
optimizer: {
enabled: true,
runs: 10000 // High for frequently called contracts
}
}
}
Gas Optimization Checklist Summary
| # | Pattern | Gas Saved | Difficulty |
|---|---|---|---|
| 1 | Use uint256 for standalone variables | 20–200 | Easy |
| 2 | Pack struct variables | 20,000+ | Medium |
| 3 | calldata vs memory | 100–1,000+ | Easy |
| 4 | Cache storage variables | 100–2,000 | Easy |
| 5 | ++i vs i++ | ~5 per iter | Easy |
| 6 | Cache array length | 3–100 per iter | Easy |
| 7 | Custom errors | 50–150 | Easy |
| 8 | immutable variables | 2,100 per read | Easy |
| 9 | constant variables | 2,100 per read | Easy |
| 10 | Short-circuit conditions | Varies | Easy |
| 11 | Avoid zero initialization | Small | Easy |
| 12 | Mappings vs arrays | Large | Medium |
| 13 | Events vs storage | 19,000+ | Medium |
| 14 | Batch operations | 42,000+ base | Medium |
| 15 | Optimizer runs setting | Varies | Easy |
How to Apply All 15 Patterns Automatically
Manually reviewing every function for these patterns is time-consuming and error-prone. The Blockhertz AI Gas Optimizer scans your Solidity contract and flags every pattern above automatically — with line-level suggestions and estimated gas savings per fix.
It works alongside the Blockhertz AI Auditor for a full security and gas review in one pass.
Gas Optimization vs Security: The Trade-Off
Some optimizations introduce risk if applied carelessly. The most common dangerous optimization is removing SafeMath checks in Solidity below 0.8.0 for performance — a pattern that contributed to the Truebit integer overflow exploit. Read the full breakdown in Truebit Integer Overflow Autopsy.
Always run a full security audit alongside gas optimization. Never sacrifice overflow protection, access control checks, or reentrancy guards for gas savings.
FAQ
Does Solidity 0.8+ make gas optimization unnecessary?
No. Solidity 0.8+ adds overflow protection automatically, but the 15 patterns above are unrelated to overflow. Storage costs, calldata efficiency, struct packing, and batching all still apply regardless of compiler version.
What is the single biggest gas saving I can make?
For most contracts, struct packing and replacing storage with events for historical data deliver the largest single-step savings — often 20,000+ gas per operation. For loops, caching storage variables and array length are the fastest wins.
Does gas optimization affect Polygon or other L2s?
Yes. While L2 fees are lower, the same patterns apply. On high-throughput L2s, gas efficiency also affects how many transactions can fit in a block — relevant for protocols under load.
Can the Blockhertz Gas Optimizer handle Vyper contracts?
Currently the Gas Optimizer is optimized for Solidity contracts. Vyper support is on the roadmap. Visit blockhertz.com/tools/gas-optimizer for the latest supported languages.
References
Resource Hub
Explore all articles →
Browse every guide on the Blockhertz blog
Explore Blockhertz
Views
4
Read Time
9 min read
Likes
0
Published
Oct 18, 2026
SIGNAL THREAD
NO SIGNALS YET — BE FIRST TO TRANSMIT
Muhammad Asif
Senior Blockchain Developer & Founder, Blockhertz
Blockchain developer and security engineer with 8+ years of experience. Founded Blockhertz in 2018 to build AI-powered tools for Web3 teams — smart contract auditing, architecture generation, gas optimization, and RWA tokenization platforms. Serving clients worldwide.