Skip to content

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.

1 · 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.

2 · 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();

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

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

4 · `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.

5 · `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.

6 · 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.

7 · 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.

8-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.

The 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.

ShareXLinkedIn#
Dezső Mező

By

Dezső Mező

Founder, DField Solutions

I've shipped production products from fintech to creator-tooling · for startups and enterprises, from Budapest to San Francisco.

Keep reading

RELATED PROJECTS

Would rather build together?

Let's talk about your project. 30 minutes, no strings.

Let's talk