---
title: "Solidity gas optimization: 16 patterns we use on audits"
description: "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."
date: 2026-04-19
updated: 2026-04-19
author: "Dezső Mező"
tags: "Blockchain, Solidity, Gas, Optimization, Audit"
slug: solidity-gas-optimization-patterns-2026
canonical: https://dfieldsolutions.com/blog/solidity-gas-optimization-patterns-2026
---

# Solidity gas optimization: 16 patterns we use on audits

Sixteen gas patterns, ranked by real-world impact on the contracts we audited in 2025.
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.

```solidity
// 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.

> **TIP:** 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.

---

Source: https://dfieldsolutions.com/blog/solidity-gas-optimization-patterns-2026
Author: Dezső Mező · Founder, DField Solutions
Site: https://dfieldsolutions.com
