All Guides
Smart ContractsSolidityGas

Gas Optimization in Solidity: A Developer's Guide

Practical gas optimization techniques for Solidity — storage packing, assembly, function selector ordering, and the EVM's cost model.

Updated March 3, 2026 8 min read

Gas optimization is both a practical necessity (cheaper transactions for users) and a signal of deep EVM knowledge. This guide covers the most impactful techniques, ranked by typical savings.

Understand the EVM Storage Model First

Storage is the most expensive EVM operation. A new storage write (SSTORE to a zero slot) costs 20,000 gas. Updating an existing storage slot costs 2,900 gas. Reading a storage slot (SLOAD) costs 100 gas (warm) or 2,100 gas (cold — first access in a transaction). These numbers explain why storage optimization is the highest-leverage area.

Storage Variable Packing

Solidity stores variables in 32-byte slots. Multiple small variables (uint128, uint64, address) can be packed into a single slot. If you read or write adjacent packed variables in the same function, you pay for one slot access instead of multiple. Order your struct members from largest to smallest to maximize packing. Example: a struct with address (20 bytes) + uint96 (12 bytes) = exactly one 32-byte slot.

Use immutable and constant

Variables set in the constructor and never changed should be immutable — they're inlined in the bytecode at deployment, making reads free (CODECOPY instead of SLOAD). Constants are even cheaper — known at compile time, inlined as literals. Never use storage for configuration values that don't change.

Cache Storage Reads in Memory

If you read the same storage variable multiple times in a function, cache it in a local memory variable. Storage reads cost 100–2,100 gas each; memory reads cost 3 gas. A loop that reads the same storage variable 100 times pays 100 × 100 = 10,000 gas; caching it first pays 2,100 + 300 = 2,400 gas.

Use Custom Errors Instead of String Reverts

require(condition, "Error message") stores the error string in bytecode. Custom errors — error InsufficientBalance(address user, uint256 balance); — cost zero storage and significantly reduce bytecode size. They also provide structured error data that's easier to decode off-chain.

Short-Circuit Boolean Evaluations

In if (cheapCheck && expensiveCheck), Solidity evaluates left to right and stops if the first condition fails. Put the cheapest check first. This is free to implement and costs nothing in complexity.

unchecked for Safe Arithmetic

Solidity 0.8+ checks for overflow on every arithmetic operation. If you can prove an operation cannot overflow (a counter in a loop bounded by array length, a subtraction inside an if-else that ensures a >= b), wrap it in unchecked { } to skip the check. Saves ~40 gas per operation.

Calldata vs Memory for Function Arguments

External function arguments declared as memory are copied from calldata to memory — an unnecessary copy. Declaring them as calldata reads them directly, saving gas. Apply this to any external function that receives arrays or structs it doesn't need to modify.

Avoid On-Chain Data You Don't Need

Events are cheap (log storage is 8 gas/byte vs 200 gas/byte for regular storage). If data is only needed off-chain (for frontend display or indexing), emit it as an event rather than storing it. Use The Graph to index events for frontend consumption.

Ready to build your Web3 project?

Tell us about your project and get a precise quote.

Get a Project Quote