DFIELDSOLUTIONS

From the archive

LabSolidity gas optimization: 16 patterns we use on audits

Storage slot packing, unchecked math, calldata over memory, custom errors, immutable vs constant: 16 gas-saving patterns that land 15-40% cuts in our audits.

From the archive9 sections · 2 min readBy Dezső Mező · Published .mdBlockchainSolidityGasOptimizationAudit

We audited 11 Solidity codebases in 2025. Every single one had 15-40% avoidable gas cost, mostly in the same patterns. Here they are in order of impact.

011 · Pack storage slots

A uint256 and a bool in separate slots cost 20k gas per write. Pack them into one slot (uint128 + bool + address) and the second write is a few hundred gas. Look for structs with mixed-size fields.

022 · Custom errors over require strings

require(x, "NotOwner") deploys the string to bytecode. error NotOwner(); revert NotOwner(); is ~50 bytes smaller and costs less at runtime.

// before
require(msg.sender == owner, "NotOwner");

// after
error NotOwner();
if (msg.sender != owner) revert NotOwner();

033 · `calldata` over `memory` for read-only params

External function arrays/structs: use calldata unless you mutate. Saves the copy + a chunk of stack.

044 · `unchecked` on safe arithmetic

Solidity 0.8+ checks every math op. Where you've already proven no overflow (post-condition from a prior require, or a bounded loop counter), wrap in unchecked { ... } for significant gas savings.

055 · `immutable` > `constant` for computed-once values

immutable is cheaper to read than constant when the value is computed at construction (like owner = msg.sender). constant wins only for literals.

066 · Short-circuit loops

for (uint i; i < n; ++i) beats i++ (pre-increment saves a few gas per iter). Cache n if it's a storage read. If the work can stop early, break · don't run the full loop.

077 · Events for historical state

Every SSTORE is ~20k gas. An emit is ~1.5k. If data doesn't need on-chain reads - historical logs, UI feeds, analytics - use events, not storage.

Run forge snapshot before and after each change. Measure, don't guess · a 'clever' optimization can cost gas via bytecode bloat.

088-16 · The shortlist

  • Use >= instead of > when semantically equivalent (saves 3 gas).
  • ++ costs less than += 1 (same reason).
  • Cache storage reads in a local variable for loops.
  • Use assembly { mstore(0x40, ...) } for hot paths only · readability cost is real.
  • Prefer type(uint256).max over hardcoded max values.
  • transfer/send are gas-stipended · call{value: ...} is cheaper and more flexible.
  • Batch similar ops into one function to share the function-selector jump cost.
  • external functions are cheaper than public when not called internally.
  • Avoid double-SSTORE on initialization · use immutable where possible.

09The anti-pattern

Don't optimize gas before you've got correctness + audit. A gas-optimized contract that reverts under edge conditions is a catastrophe, not a win. Correctness first, then gas · in that order.

What to take away

  • The 16 gas-optimization patterns we apply on every audit, ranked by real-world impact. 15-40% reductions typical on contracts that haven't been gas-reviewed.
We build this for clientsBlockchain

More from the lab

Browse all entries

Want this looked at on your own system?Start a conversation

5.0
“New shop, more orders in the first week.”Müller Márk · GeekCornerManaging Director · Hungary