Gas Optimization Tricks I Use on Every Solidity Contract
SolidityWeb3Security

Gas Optimization Tricks I Use on Every Solidity Contract

Ten gas optimization patterns I apply to every Solidity contract before mainnet — storage packing, calldata, custom errors, immutable, unchecked, and the ones that actually move the needle.

HJ
Hassan Javed
February 2026
9 min read

Why this is worth your time

A user pays for every gas unit your contract consumes. A 30 percent gas reduction is a 30 percent cost reduction for every user, forever. Compounded across millions of transactions, that's serious money.

These ten patterns are what I apply to every Solidity contract before deployment. None of them require advanced knowledge; all of them measurably reduce gas.

1. Pack storage variables

Storage slots are 32 bytes. Multiple smaller variables can share a slot. Solidity packs them automatically if they're declared together and fit.

If you declare uint256 timestamp, address owner, uint96 amount as three separate slots, you use 3 slots. Reorder so address owner (20 bytes) and uint96 amount (12 bytes) sit together — they share one slot. Saves ~22,000 gas per write that would have hit the extra slot.

Apply this once when designing storage layout; the savings persist forever.

2. Use calldata for read-only function parameters

For external functions with array or struct parameters you don't modify, calldata saves significant gas. Compared to memory, ~200 gas per array element on typical calls.

3. Custom errors instead of require strings

Instead of require with an error string (which is stored on chain), define a custom error and revert with it. Saves ~50 gas on revert and more importantly cuts deployment cost.

4. immutable for constructor-set values

Variables set once in the constructor and never changed should be immutable, not regular storage. Saves ~2,000 gas per read.

5. unchecked blocks where overflow is impossible

Solidity 0.8+ checks for overflow on every arithmetic operation. Sometimes you've already validated bounds and the check is redundant. In a loop where i is bounded, wrap the increment in unchecked. Saves ~50 gas per iteration. Add up across a 1000-iteration loop, you save 50K gas.

Use carefully. Only where overflow is provably impossible.

6. Cache storage reads in memory

Every storage read costs ~2,100 gas (cold) or ~100 gas (warm). Don't read the same storage variable twice in one function — read once into a local memory variable, then reuse. Saves ~100 gas per redundant read.

7. Short-circuit operations

When using && or ||, put the cheapest condition first. If it determines the result, the expensive one is never evaluated. Check user is not address zero before checking storage balance.

8. Reduce contract size with library calls

Contract deployment cost scales with bytecode size. If you have rarely-called complex logic, move it to a library. Saves on deployment cost. Marginal effect at runtime, real effect on deploys.

9. Use events instead of storage for historical data

If you only need to read historical data off-chain (analytics, indexers), emit an event instead of storing in contract state. Events cost ~375 gas plus ~8 gas per byte. Storage costs ~20,000 gas per slot. Order-of-magnitude savings.

10. Batch operations

Instead of N separate transactions, accept arrays and batch. For N transfers, batching saves ~21,000 gas per transfer in tx overhead. At N=10, that's 210K gas saved.

Patterns I no longer use

A few tips you'll see in older guides that have been outdated:

Bit-packing booleans manually. Solidity does it for you in struct definitions now. Don't write uint256 flags and bit-shift unless you really need it.
Using shorter variable names. Variable names don't affect deployed bytecode. Use descriptive names.
Removing comments. Comments are stripped at compile time.
i++ vs ++i. The difference is negligible in 0.8+ with the optimizer enabled.

Measure, don't guess

The optimizer in Solidity 0.8 is good. Some "optimizations" that mattered in 0.4 don't anymore. Always measure with Hardhat's gasReporter or Foundry's forge snapshot.

Workflow: write the contract naturally, take a gas snapshot of all the tests, apply optimizations one at a time, re-snapshot and compare. You'll discover that some "optimizations" don't help in your specific case. Skip those.

How much can you save in total?

Across these 10 patterns, on a typical staking or AMM contract, I usually shave 20-40 percent off the gas cost of the most-called functions. On a contract with high transaction volume, that's serious user cost saved.

TL;DR

Pack storage, use calldata, prefer custom errors over require strings
immutable for constructor-set values, unchecked for safe arithmetic
Cache storage reads, short-circuit checks, use events for historical data
Batch operations to amortize transaction overhead
Measure with Hardhat gasReporter or Foundry; don't guess

If your contract needs a gas optimization pass before mainnet, contact me. Solidity work is part of my service offering, and gas review is something I do on every contract before deployment.

Related Reads

You might also like