Skip to main content
Solidity Gas Optimization: A Benchmarked Checklist (and Where DIY Stops Paying Off)
blockchainsoliditysmart-contracts+5

Solidity Gas Optimization: A Benchmarked Checklist (and Where DIY Stops Paying Off)

A benchmarked Solidity gas optimization checklist covering storage packing, caching, and calldata—cutting transaction costs by 60-70%, with DIY limits explained

The highest-impact Solidity gas optimizations, in priority order: pack storage variables into shared 32-byte slots, cache storage reads in memory and write once, log history with events instead of arrays, use mappings rather than arrays for lookups, pass read-only inputs as calldata not memory, and mark fixed values as constants or immutable. Applied together, these cut a typical transaction’s cost by roughly 60-70%.

That number matters because every operation your contract runs is paid for in real money by the person clicking the button. A bloated contract quietly taxes every user, every transaction, forever. Most of the savings come from a handful of predictable moves, and you can measure each one before and after. The rest of this post walks the checklist in order of payoff, then draws the honest line where hand-tuning stops being worth your engineering hours.

What actually drives gas costs in a Solidity contract?

Three things dominate the bill: touching permanent storage, doing computation, and the size of the data you send in. Storage is by far the most expensive. Writing a fresh value to permanent storage costs 20,000 gas; reading one costs 2,100. Computation and calldata are comparatively cheap. So the single most useful mental model is this: every time your code writes to storage, picture a cash register ringing up 20,000 units, and every time it reads, 2,100. Optimization is mostly the art of ringing that register less often.

Why lead with this? Because it tells you where not to spend effort. Shaving a few arithmetic operations while your contract writes to storage inside a loop is like tightening a faucet while the roof leaks. Find the storage operations first.

How does storage packing lower gas costs?

Solidity stores data in 32-byte slots, and it fills each slot from the right. Declare four full-size 256-bit numbers and you use four slots. But many values don’t need the full width — a boolean is one byte, an Ethereum address is 20 bytes, a counter might fit in 16. If you order your variables so several small ones sit together, the compiler packs them into a single slot.

The source’s example makes the math concrete. An unoptimized contract that spread a counter, a max value, a boolean, and a balance across four slots gets rewritten to fit an address, a 96-bit balance, a 32-bit timestamp, and a boolean into one slot — a 75% reduction in storage slots for that group. The catch worth knowing: order matters. Put the boolean between two full-size numbers and the packing breaks. Group your small types as neighbors, largest logical groupings first, and let them share a slot.

For a startup minting an NFT collection or running a token, this is free money. The layout costs nothing at runtime to design well — it just costs a careless developer real gas to design badly.

How do you cut storage reads and writes?

The biggest headline savings live here, and it comes down to one discipline: read from storage once, do your work in cheap memory, write back once.

The source’s loop example is the clearest illustration. A batch-mint function that incremented a storage counter inside a loop cost about 2,000,000 gas for 100 mints — because it hit that 20,000-gas write on every pass. Rewritten to read the counter into a local variable, add to it in memory, and write back a single time, the same 100 mints cost roughly 44,000 gas. That’s a 97.8% reduction, according to the source’s figures, from one structural change.

Two more moves belong in this bucket:

Log history with events, not arrays. Pushing a transfer record into a stored array costs 20,000+ gas each time. Emitting an event instead costs about 375 gas — roughly 98% cheaper — and off-chain tools can still read the full history. If your contract is storing data purely so a dashboard can display it later, that data almost always belongs in an event.

Use mappings for lookups, not arrays. Checking whether an address is registered by looping through an array means one storage read per entry; a mapping answers the same question in a single read regardless of size. The source frames it as 100 reads versus 1 for 100 users.

Mark fixed values as constant or immutable. A value baked into the contract’s bytecode costs about 21 gas to read, versus 2,100 for a regular storage variable — the source puts it at roughly 100x cheaper. Anything set once at deployment and never changed should be one of these.

Which loop, function, and calldata tricks compound at scale?

The smaller optimizations rarely matter on a single call. They matter because contracts run thousands of times, and the savings multiply.

Calldata over memory for read-only inputs. When a function only reads an incoming array and never modifies it, declaring the parameter as calldata lets the code read straight from the transaction data. Declaring it as memory forces an upfront copy, and memory cost grows quadratically as the data gets larger, per the source. For a function that accepts a big list of recipients, that copy is pure waste.

Batch operations. Ten separate transfers means ten transactions, each paying its own base overhead. One batched function that loops through recipients pays that overhead once — the source estimates a 90% reduction in transaction overhead for batch operations. If you’re airdropping tokens or settling many payments, batching is the difference between a viable feature and one users refuse to touch.

Correct visibility and early validation. Marking helper functions internal instead of public avoids extra call overhead, and running your cheap require checks before any expensive computation means users who submit invalid input don’t pay for work that was going to fail anyway. Neither is glamorous. Both are free.

If you run a token contract with steady daily volume, applying the storage-caching and event changes alone lands you most of that 60-70% per-transaction saving the source reports on its worked token example. The calldata and batching wins stack on top for specific high-throughput functions.

Are custom errors, unchecked math, and assembly worth it?

These are the advanced tier, and the honest answer is: sometimes, with care.

Custom errors replace the human-readable strings inside require statements. Those strings get stored in your contract and cost gas both to deploy and to trigger; a named error carries the same meaning far more cheaply. This one is low-risk and worth adopting broadly.

Unchecked math skips the automatic overflow protection the compiler adds to every arithmetic operation. Inside a loop counter you know can’t overflow, wrapping the increment in an unchecked block removes a check on every pass. The savings are real but the risk is also real — use it only where you can prove the numbers stay in range, never on user-controlled values.

Inline assembly lets you write low-level operations by hand for maximum efficiency. It can squeeze out gas the compiler leaves on the table, but it discards the safety guarantees that make Solidity survivable, and it’s where audits find some of their nastiest bugs. For most teams this is a step too far for a marginal gain.

Notice the pattern: as you move down the list, the gas saved per line shrinks and the odds of introducing a security bug rise. That curve is the whole point of the next section.

When should you bring in a smart contract audit instead of optimizing yourself?

DIY optimization delivers most of its value at the top of the list and hits diminishing returns fast at the bottom. Storage packing, caching, events, mappings, immutable values — a competent developer captures nearly all of the 60-70% here with standard tooling like the Hardhat gas reporter and the built-in Solidity optimizer the source recommends. That’s the work every team should do in-house.

The line to watch is where optimization starts trading against safety. Unchecked math and assembly save gas by removing protections, and a contract holding real user funds cannot afford a clever gas trick that opens a reentrancy hole or an overflow. A professional smart contract audit earns its fee exactly here — pairing gas optimization with threat modelling and a full test suite, so you get the savings without gambling on the security. On a contract processing millions in volume, an audit that prevents a single exploit pays for itself many times over, and the gas savings are a bonus on top.

If you’re weighing the spend against the size of your build, our scoping guide to custom blockchain costs breaks down where audit budget fits against the rest of a project. Audits are shifting from a launch-day formality toward a continuous practice, because gas and security are the same optimization problem viewed from two angles — and teams that treat them together ship cheaper, safer contracts than the ones that bolt on a review at the end.

FAQ

Q: What are the most effective Solidity gas optimization techniques? A: The highest-return techniques are storage packing (fitting small variables into shared 32-byte slots), caching storage reads in memory to write only once, using events instead of arrays for history, choosing mappings over arrays for lookups, and marking fixed values as constant or immutable. The source reports these collectively reduce transaction costs by roughly 60-70%.

Q: Why is storage so expensive in Ethereum smart contracts? A: Writing a new value to permanent storage costs 20,000 gas and reading costs 2,100, far more than computation or passing data in. Storage is permanent state that every network node must keep, so Ethereum prices it accordingly. Most optimization is really about touching storage less often.

Q: When is a professional audit worth it over doing gas optimization yourself? A: Capture the top-of-list wins yourself — packing, caching, events, immutables — with standard tooling. Bring in an audit once you’re reaching for techniques that trade safety for gas, like unchecked math or assembly, or when the contract holds significant user funds where a single exploit would dwarf any gas savings.

Key Takeaways

  • Find and reduce storage operations first; at 20,000 gas per write versus 2,100 per read, they dwarf every other cost and deliver the biggest wins.
  • Adopt the read-once-write-once pattern in every loop — the source shows it cutting a 100-mint batch from ~2,000,000 to ~44,000 gas.
  • Move history logging into events and lookups into mappings before touching anything more exotic; these are low-risk and high-return.
  • Save unchecked math and assembly for cases where you can prove correctness, because their gas savings come by removing safety guarantees.
  • Measure everything with a gas reporter before and after, and bring in an audit at the point where optimization and security become the same decision. If you’re running contracts at real volume, a free gas audit is the cheapest way to find out how much you’re leaving on the table.

Have a project in mind?

Fixed price after a paid discovery — no hourly billing. A real engineer reads every enquiry, and we reply within 24 hours.