A Friendly Guide to Optimizing Gas Fees in Solidity

Table of contents
- What is Gas in Ethereum?
- Why Should You Optimize Gas in Solidity?
- Deeper Dive: How Solidity Code Consumes Gas
- 1. Use Mappings Instead of Arrays
- 2. Enable the Compiler Optimizer
- 3. Minimize On‑Chain Data
- 4. Use Indexed Events
- 5. Be Careful with uint8 and Small Types
- 6. Pack Variables
- 7. Free Up Unused Storage
- 8. Prefer calldata Over memory for Read‑Only Arrays
- 9. Use immutable & constant
- 10. Use external Instead of public for External Calls
- Final Tip: Always Test Gas Usage
- Summary Table

Ethereum gas fees remain a top concern—even after its shift to Proof‑of‑Stake. Gas optimization is more than just a hack—it’s a core discipline of smart contract engineering. This article breaks down the "what", "why", and "how" of gas optimization in Solidity with real-world examples, theory, and 10 essential best practices you can use today to reduce Ethereum transaction costs and improve your contract performance.
By the end, you’ll know how to write cleaner, faster, and cheaper Solidity code—and help the entire ecosystem along the way.
What is Gas in Ethereum?
In the Ethereum ecosystem, gas is the unit used to measure computational effort. Every operation in the Ethereum Virtual Machine (EVM) consumes gas, whether it's adding two numbers, storing data, or calling another contract.
Think of gas like fuel: the more complex the task, the more fuel it requires. You pay for this fuel in ETH, and prices vary based on network demand.
Without gas, Ethereum smart contracts couldn’t operate—it’s what powers every decentralized interaction.
Why Should You Optimize Gas in Solidity?
Gas fees have two major pain points:
Cost to Users: High gas fees make your dApp unaffordable.
Block Limits: Ethereum blocks have a gas cap. Contracts using too much gas can fail or be excluded from blocks.
Optimizing gas:
Reduces user friction
Saves ETH for you and your users
Makes your contract more scalable
Prevents out-of-gas errors
Encourages cleaner, more maintainable code
💡 Gas optimization isn’t just about reducing costs—it’s about writing smarter, faster, more secure smart contracts.
Deeper Dive: How Solidity Code Consumes Gas
Solidity compiles down to byte code, which runs on the EVM. Each line of Solidity translates into one or more EVM opcodes—basic machine instructions like ADD
, STORE
, CALL
.
Each opcode has a fixed gas cost. For example:
Opcode | Description | Gas Cost |
ADD | Addition | 3 |
SSTORE | Store in storage | 20,000+ |
CALL | External function call | 700–2600 |
The SSTORE
opcode is one of the most expensive. That’s why you'll often see optimization strategies focused on minimizing storage operations.
1. Use Mappings Instead of Arrays
Arrays require iteration. Mappings act like hash maps and offer O(1) lookup time without iteration. Since EVM charges gas for every computational step, avoiding loops saves gas.
// Less Optimal
string[] public cities = ["NY", "Moscow", "HK"];
// Optimized
mapping(uint => string) public cities;
Use mappings whenever iteration isn’t needed—access is O(1), and it’s cheaper than looping through arrays.
2. Enable the Compiler Optimizer
The compiler optimizer simplifies expressions, inlines functions, and minimizes opcode count. More optimization runs = better runtime efficiency, but bigger contract size.
Config (Hardhat):
optimizer: {
enabled: true,
runs: 10000
}
Higher runs
mean more deployment cost, but cheaper execution later on. Low runs
is the opposite. Find the right balance. Use runs: 200
for short-lived contracts; use 10000+
for production-grade contracts used frequently.
3. Minimize On‑Chain Data
Storage is the most expensive part of Ethereum. Instead of storing everything on-chain, keep large data structures off-chain and only store critical state.
Tactics:
Only store essential state on‑chain.
Use off‑chain storage and on‑chain verification (oracles…).
Use IPFS, The Graph, or Chainlink for external data.
Batch transactions using dynamic arrays.
Avoid loops in your contract, they can exceed block gas limits.
Batch operations to rebuild loops outside—e.g.,
function batchSend(Call[] calldata calls) external { … }
4. Use Indexed Events
Index your event fields to let off‑chain tools track contract activity cheaply:
event Transfer(address indexed from, address indexed to, uint256 value);
Indexed events are searchable and don’t bloat your contract state. Don’t use events for storing critical state—they are not accessible on-chain by contracts.
5. Be Careful with uint8
and Small Types
The EVM works in 32-byte words. Using smaller types (e.g. uint8
, uint16
) individually can cost more because they need to be padded to 32 bytes.
Use smaller types only when packing multiple values.
6. Pack Variables
EVM packs consecutive small variables into the same storage slot. State variables are stored in 32-byte slots. If multiple variables fit into a single slot, EVM can store them together, saving gas.
Bad example:
// Not packed
uint128 a;
uint256 b;
uint128 c;
Optimized:
// Packed
uint128 a;
uint128 c;
uint256 b;
Keeps storage compact = less gas.
7. Free Up Unused Storage
Resetting variables or using delete
refunds gas because it clears state storage.
delete myStruct;
delete myVar;
myInt = 0;
(Note: mappings don’t auto-delete—delete entries manually.)
Mappings don’t benefit from this, but structs and arrays do.
8. Prefer calldata
Over memory
for Read‑Only Arrays
Memory is copied from calldata. If you're not modifying a parameter (e.g. arrays), using calldata
avoids unnecessary copying.
function process(uint[] calldata arr) external { … }
Saves gas, especially with large inputs.
9. Use immutable
& constant
constant
and immutable
variables are inlined or stored cheaper. Saves gas!
solidityCopy codeuint256 constant FEE = 10;
uint256 immutable owner;
Immutable: Set once during deployment.
Constant: Fixed at compile time.
10. Use external
Instead of public
for External Calls
function foo() external view returns (...) { … }
external
is cheaper for public-facing functions. Use public
only when internal calls are necessary.
Final Tip: Always Test Gas Usage
Use tools like Hardhat gas reporter and test in Rinkeby/Mainnet fork to see real costs—especially for public functions like mint()
.
Use tools like:
Hardhat’s
gasReporter
Remix’s compiler estimates
Tenderly’s gas profiler
Focus optimization on functions called frequently—like mint
, buy
, vote
, etc.
Summary Table
Optimization Strategy | Benefit |
Mappings vs Arrays | O(1) lookup, no loops |
Compiler Optimizer (runs ) | Smarter, leaner bytecode |
Minimize On‑chain Storage | Lighter state, lower costs |
Indexed Events | Searchable, efficient logging |
Avoid Small Integers in Loops | Use 256‑bit for performance |
Variable Packing | Compact storage |
Delete Unused Storage | Gas refunds |
calldata over memory | Cheaper parameter passing |
constant & immutable | Inlined/optimized storage |
external functions | Lower call overhead |
Optimizing gas in Solidity isn’t a “nice to have.” It’s part of writing professional-grade smart contracts. Your users will thank you, your protocol will scale better, and you’ll reduce your project’s environmental and economic cost.
Start small: pick one tip and apply it today. Once you test and confirm savings, move to the next. Before you know it—you’re writing top-tier optimized contracts!
Subscribe to my newsletter
Read articles from Harsh Panghal directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
