Key Takeaways
- Measure gas before changing code so every optimization targets real bottlenecks instead of simple guesses.
- Persistent storage is often the costliest area, so fewer reads and writes usually save the most gas.
- Calldata, constants, immutables, custom errors, and smart storage design cut needless execution and deployment costs.
- Modern Solidity compilers already handle many optimizations, so older manual tricks may offer little real benefit.
- Compiler settings need benchmarking because deployment size and runtime gas often pull in opposite directions.
- Layer 2 networks change priorities, so always profile gas on the chain where your contract will live.
- Never trade correctness, security, or readability for tiny savings that users will barely notice in practice.
Solidity gas optimization means reducing unnecessary EVM work while keeping the same behavior and security. It is not about writing short or clever code, but about making every transaction cheaper to run. Your fee roughly equals gas used multiplied by the effective gas price set by network demand.
Code changes control the gas used, while the market controls how much each unit of gas costs. Below, you will learn which operations cost the most and which Solidity gas optimization techniques actually work today. We cover storage, calldata, loops, custom errors, transient storage, compiler settings, Layer 2 costs, and common myths.
Every step follows one simple workflow that keeps savings real: measure, optimize, test, and then measure again. If contract execution feels new, learning how smart contracts work first makes every section below easier. The goal is simple: a contract that stays correct, secure, readable, and measurably cheaper for its users.
What Actually Consumes Gas in a Solidity Contract?
Smart contract gas costs come from several EVM operations, but persistent state, external calls, calldata, and execution work deserve the closest attention first.
Contract Deployment
Deploying a contract costs gas because the network must store its bytecode and run the constructor once. Larger bytecode and heavy constructor logic both raise the bill before any user ever calls a function.
Persistent Storage
Reading storage costs gas, but writing storage costs far more, and creating brand new state costs the most. Solidity documentation advises keeping persistent state limited to what the contract truly needs for its main job.
Computation and Memory
Arithmetic, hashing, memory allocation, and data encoding all use EVM resources whenever a function runs on-chain. These steps still cost gas, yet they are usually much cheaper than unnecessary reads and writes to storage.
External Contract Calls
Each call to another contract adds overhead and may trigger expensive logic you cannot see in your code. Repeated calls inside one transaction can quietly multiply costs, especially when the other contract touches its storage.
Calldata
Transaction input data also affects the fee, because every byte sent to the contract carries a cost. On rollups, posting this data to Ethereum can form a large share of the total transaction fee.
Relative Gas Costs by Operation
Gas costs differ across operations and networks, but these relative levels show where optimization can have the greatest impact.
Area | Relative Cost | Why It Matters |
Storage writes | High | Changes permanent state kept by every node |
Storage reads | Relatively high | First access in a transaction costs more |
External calls | Context dependent | Depends on the logic inside the called contract |
Memory and computation | Usually lower | Temporary work inside one call |
Calldata | Depends on size and network | Weighs more heavily on rollups |
Measure Gas Before You Optimize
Measure gas before optimizing so your Solidity gas optimization techniques target real bottlenecks, not assumptions about which lines of code seem expensive.

Establish a Gas Baseline
Before touching code, record deployment gas, frequently called functions, expensive transactions, and worst-case execution paths. This baseline becomes your reference point, so you can prove whether each change truly helped or hurt later.
Find the Functions Users Call Most
Prioritize your work by multiplying how often a function runs by how much gas each call consumes. A function saving 100 gas across millions of calls beats a rare admin function that saves several thousand.
Use Foundry Gas Reports
A Foundry gas report shows the minimum, average, median, and maximum gas for each tested function call. If you are still choosing a toolkit, see how Foundry stacks up among other smart contract development tools.
Use Gas Snapshots
A Forge gas snapshot saves gas usage per test, giving you a clean record to compare against later. The loop is simple: take a baseline, change the code, snapshot again, and compare both results.
Keep Compiler Settings Constant During Comparison
Compare like with like by keeping the Solidity version, optimizer settings, optimizer runs, and IR unchanged. The EVM target and test inputs must also match, or your benchmark may show savings that never existed.
Start With Contract Design Before Micro-Optimizing Code
Strong contract architecture can remove unnecessary operations before you start making smaller code-level optimizations.
Store Only What the Contract Needs
Do not persist data that can be derived, cheaply recomputed, indexed from events, or safely kept off-chain. Solidity documentation itself recommends limiting persistent storage because it remains one of the costliest EVM resources.
Avoid Unbounded On-Chain Work
Loops over arrays that can grow forever may eventually exceed the block gas limit and freeze key features. Pagination, pull patterns, user-driven claims, bounded batches, and separate transactions keep each call safely predictable.
Avoid Duplicate State
Avoid storing any value that existing state can already produce through a cheap and simple calculation. Cache it only when the saved reads clearly outweigh the extra cost of keeping both copies updated and correct.
Choose Data Structures for the Access Pattern
Mappings suit direct keyed lookups, while arrays suit cases where ordered iteration is genuinely required by the logic. Combine both only when each capability is truly needed, because extra structures mean extra storage to maintain.
Reduce Expensive Storage Operations
Solidity storage optimization usually offers some of the largest practical savings, especially when unnecessary reads, writes, and storage slots are removed.
Reduce Repeated Storage Reads
When a function reads the same storage value several times, test reading it once into a local variable. Measure with your current compiler, because the optimizer may already remove some redundant reads on its own.
Reduce Unnecessary Storage Writes
Skip a write when the value has not changed or does not need to persist after the transaction. Where it is safe, combine several updates into one write instead of touching the same slot repeatedly.
Pack Storage Variables Carefully
Solidity storage packing lets smaller values share one 32-byte slot when they are declared next to each other. For example, two uint128 variables placed together can fit inside a single slot instead of using two.
Don't Assume Smaller Types Are Always Cheaper
The EVM works on 32-byte words, so operations on smaller types can sometimes require extra conversion steps. Packing two uint128 values helps, but swapping every uint256 local variable for uint8 usually does not.
Pack Structs When the Access Pattern Supports It
Ordering struct members from similar sizes together can reduce the number of slots each struct instance occupies. However, updating one member of a packed slot may need extra read and modify work behind the scenes.
Use Constants and Immutables Where Values Never Change
Use constants for values known at compile time and immutable for values set once inside the constructor. Neither uses a normal storage slot, which explains why Solidity constant gas and Solidity immutable gas savings matter.
Use Calldata, Memory, and Storage Efficiently
Choosing between calldata, memory, and storage affects copying and persistence costs, making data-location decisions an important part of Solidity gas optimization.
Use calldata for Read-Only External Inputs
For external reference type parameters you never modify, calldata avoids copying the whole argument into memory first. Solidity documentation recommends calldata wherever possible, since skipping that copy saves gas on every single call made.
Use memory for Temporary Mutable Data
Memory lasts only for the current call, which makes it the right home for data you must change. It is cleared when execution ends, so nothing written there will ever reach permanent contract state.
Use storage References Deliberately
A storage reference points directly at persistent state, so any change through it is permanently saved on chain. Use it only when you truly need to read or update stored data, not for temporary work.
Avoid Unnecessary Data Copies
Watch for copies from calldata to memory, storage to memory, and large structs or dynamic arrays moving around. Each copy costs gas, so measure whether the function really needs one before keeping it in place.
Here is a quick comparison of the four main data locations and when each one fits best.
Data Location | Lifetime | Mutable? | Typical Use |
calldata | Call | No | Read-only external inputs |
memory | Call | Yes | Temporary working data |
storage | Persistent | Yes | Contract state |
transient storage | Transaction | Yes | Temporary cross-call state |
Use Transient Storage for Transaction-Scoped State
Solidity transient storage adds a modern option for data that only needs to live during one transaction. It resets automatically when the transaction ends, so it avoids the lasting costs of permanent storage writes.
When Transient Storage Makes Sense
It fits transaction-scoped data such as reentrancy locks, temporary flags, and intermediate state shared across calls. These values matter only briefly, so paying for permanent storage writes would simply be wasted gas and effort.
When It Does Not Replace Memory
Memory belongs to one call frame and disappears when that call returns to its caller in the transaction. Transient storage survives across calls within the same transaction, which makes it useful for shared temporary state.
Check Network and EVM Compatibility
Transient storage needs an EVM version that supports EIP 1153, which arrived with the Cancun upgrade or later. Confirm your target chain supports it before relying on it, because not every EVM network has adopted it.
Make Functions and Reverts More Gas Efficient
Efficient function design reduces repeated work, unnecessary calls, and costly failure paths while keeping frequently executed Solidity gas-efficient code easy to maintain.

Use Custom Errors Instead of Long Revert Strings
Custom errors encode compactly, and they avoid storing long revert messages inside your contract bytecode during deployment. Custom errors are generally cheaper than long revert strings because they use less bytecode and encode failures more compactly.
Avoid Repeating Expensive Calculations
If a function calculates the same value several times, compute it once and reuse the stored local result. Always measure the change, because the compiler sometimes removes this repetition before you even touch the code.
Order Cheap Checks Before Expensive Work
Where behavior stays identical, reject invalid input before storage changes, external calls, or heavy calculations begin. Failing early means a bad transaction wastes far less gas, which also protects your users from paying for nothing.
Use Short-Circuit Evaluation Deliberately
In an expression such as cheapCheck && expensiveCheck, if the first condition is false, the second condition is not evaluated. Put the cheaper or more likely to fail condition first, as long as the logic is the same.
Minimize Unnecessary External Calls
Reuse data you already hold instead of calling another contract repeatedly for the same value within one function. Never remove a call when fresh state is required, because stale data can break logic and create risk.
Optimize Loops and Batch Operations Safely
Loop optimization should reduce repeated work without weakening safety or making execution unpredictable.
Keep On-Chain Loops Bounded
Avoid looping over user-controlled arrays that can grow without limit as more people use the contract. The danger is not only cost, since the transaction may eventually exceed the block gas limit entirely.
Avoid Repeating Expensive Work Inside the Loop
Move calculations that never change outside the loop so the contract does not repeat them every iteration. Also limit storage access and external calls inside the loop, since each repetition multiplies their total cost.
Use unchecked Only When Safety Is Proven
Solidity 0.8 and later checks arithmetic overflow by default, and unchecked blocks remove those safety checks entirely. Use unchecked only when overflow is mathematically impossible, because careless use can introduce serious, costly, and hidden vulnerabilities.
Know What Modern Compilers Already Do
Since Solidity 0.8.22, the compiler can automatically use unchecked increments in standard for loops when overflow is impossible. So blindly wrapping every loop increment in an unchecked block often adds clutter without any real savings.
Batch Transactions Only When It Makes Sense
Batching can reduce repeated transaction overhead, but very large batches can hit gas limits and fail. Larger batches also increase the scope for failure and make error handling more complex, so always benchmark your actual workload before you decide.
Optimize Events and On-Chain Data
Events can reduce persistent state when data is needed only by off-chain systems, but they cannot replace values that future contract logic must read.
Use Events for Off-Chain Observability
If indexers, analytics tools, frontends, or monitoring systems are the only consumers, an event often beats storage. Logging data this way avoids keeping redundant persistent state that the contract itself will never read again.
Don't Replace Required Contract State With Events
Smart contracts cannot read historical event logs during execution the way they read normal persistent storage values. If future contract logic depends on a value, that value still needs to live safely in contract state.
Keep Event Payloads Purposeful
Every logged byte costs gas, so avoid emitting large or repeated data that no consumer actually uses. Decide who reads each event first, then include only the fields that reader genuinely needs to function.
Configure the Solidity Compiler for Gas Optimization
The Solidity compiler optimizer can remove redundant work across generated code, but benchmarks are still needed to choose settings that fit deployment and runtime usage.

Enable the Solidity Optimizer
The optimizer works across code generation, the Yul intermediate representation, and final opcodes to remove wasted work. It can lower both runtime execution cost and bytecode size, making it the easiest global improvement available today.
Understand optimizer_runs
Solidity optimizer runs do not mean running the optimizer many times, despite what the setting name might suggest. Lower values favor smaller bytecode and cheaper deployment, while higher values favor cheaper code for frequently repeated runtime calls.
Test via_ir
Solidity via IR compiles through Yul, enabling powerful optimizations that can work across separate function boundaries. It does not always produce cheaper code, so benchmark deployment gas, hot function gas, and final bytecode size together.
Use an Appropriate EVM Target
Newer EVM versions support opcodes and features that older targets cannot use, which affects final gas costs. Always match your evmVersion setting to the networks where you deploy, or contracts may fail or behave unexpectedly.
Retest When Upgrading Solidity
New compiler releases often add optimizations that make older manual gas tricks unnecessary or even slightly harmful today. After upgrading, rerun your tests and gas benchmarks, then remove the workarounds the compiler now handles better.
Balance Deployment Gas Against Runtime Gas
Some changes lower deployment cost but raise call costs, while others enlarge bytecode to make calls cheaper.
Optimize Frequently Used Contracts for Runtime
Protocols with millions of interactions benefit most when each call becomes cheaper, even if deployment costs rise a little. Recurring savings for users quickly outweigh a slightly larger deployment bill that your team pays only once.
Optimize Factories and Frequently Deployed Contracts for Creation Cost
Systems that create many contract instances pay deployment costs again and again with every new copy they launch. For these factories, smaller bytecode and lower optimizer runs values often matter far more than runtime savings.
Measure the Contract's Actual Usage Pattern
Do not copy one optimizer runs value into every contract just because another project used that number. Study how often each contract deploys and how often users call it, then tune settings to match.
Gas Optimization on Layer 2 Networks
Layer 2 networks have different fee structures, so optimization priorities can shift from execution costs to data and transaction overhead.
Execution Gas Still Matters
Rollups still execute Solidity code on an EVM, so storage writes and heavy computation continue to cost gas. Every technique covered earlier still applies, even when execution fees are much lower than on mainnet.
Calldata Can Matter More
Rollups must post or make transaction data available on Ethereum, which can dominate the total fee paid. Trimming input size and using compact encoding may save more there than shaving a few execution steps.
Optimize for the Actual Target Chain
The best saving on Ethereum mainnet may not be the best one on Arbitrum, Base, Optimism, or other chains. Profile your contracts on the intended network so your effort targets the costs real users will actually pay.
Advanced Gas Optimizations: Use With Caution
Advanced EVM gas optimization can produce worthwhile savings in narrow hot paths, but these techniques increase complexity and should follow profiling, testing, and review.
Bitmaps and Packed Representations
- Main Benefit: Many boolean or small values can share one storage word, cutting slot usage dramatically in some designs.
- Main Risk: Encoding and decoding logic becomes harder to read, test, and audit correctly over the long term.
Inline Assembly and Yul
- Main Benefit: Assembly gives lower-level control when Solidity cannot generate efficient enough code for a hot path.
- Main Risk: It raises audit effort, maintenance risk, and the chance of subtle memory or storage mistakes.
Specialized Data Encoding
- Main Benefit: Custom encoding can shrink calldata or storage, which helps most on rollups with expensive data costs.
- Main Risk: Extra decoding complexity and weaker compatibility with standard tools can outweigh the gas you save.
Gas Optimization Advice That Can Be Misleading
Some gas optimization tips are repeated so often that they sound universal, but their impact depends on how your contract is written and executed.
"Always Use the Smallest Integer Type"
- Common Belief: Swapping uint256 for smaller integer types everywhere will always make your contract cheaper to run.
- The Reality: Smaller types help mainly when they enable packing, otherwise, word-size conversions can add extra work.
"View and Pure Functions Are Free"
- Common Belief: View and pure functions never cost anything, no matter where or how they are called.
- The Reality: They are free only off-chain, because on-chain calls from other contracts still consume gas.
"Always Use Mappings Instead of Arrays"
- Common Belief: Mappings are always cheaper than arrays, so arrays should be avoided in every serious contract.
- The Reality: The right choice depends on whether your logic needs direct lookup, ordering, or full enumeration.
"Always Use unchecked in Loops"
- Common Belief: Every loop counter should be wrapped in unchecked to squeeze out the maximum possible gas savings.
- The Reality: Modern compilers already handle many safe increments, and careless unchecked math can create real vulnerabilities.
"Always Cache Everything in Memory"
- Common Belief: Copying every storage value into memory first is always the cheapest approach for any function.
- The Reality: The copy itself costs gas, so measure whether repeated reuse really justifies making that extra copy.
"Events Can Replace Storage"
- Common Belief: Emitting events is a cheap and complete replacement for storing important data inside contract state.
- The Reality: Events work only when no smart contract ever needs to read that value again later.
"Assembly Is Always More Efficient"
- Common Belief: Hand-written assembly always beats Solidity, so serious teams should use it for every function.
- The Reality: Even when assembly saves gas, the security and maintenance costs may outweigh such small and narrow benefits.
Measure the Gas Savings and Prevent Regressions
Gas optimization is successful only when measurements prove a real improvement without changing behavior, security, deployment assumptions, or important user-facing costs

Rerun the Gas Benchmark
Compare the same function using the same compiler, the same settings, the same starting state, and identical inputs. Any difference in these conditions can hide a regression or invent a saving that does not exist.
Run the Full Test Suite
Your optimized contract must behave exactly like the original, so run every existing test after each change. Strong smart contract testing habits catch broken logic early, long before a cheaper bug reaches real user funds.
Check Security-Sensitive Changes
Be very careful when making changes to the arithmetic, storage layout, assembly, access logic or any external call behavior in the code. For upgradeable contracts, storage layout changes require extra care because mistakes can silently corrupt existing data forever.
Add Gas Checks to CI
Use Forge gas snapshots or benchmark thresholds in continuous integration to catch expensive regressions before any merge. This keeps every future pull request honest, so your hard-won savings do not slowly disappear over time.
Where Should You Optimize First?
Use this priority order to spend effort where returns are highest, starting with architecture and ending with assembly.
Priority | Area | Why |
1 | Architecture and unnecessary work | Removing operations usually gives the largest savings |
2 | Storage reads and writes | Persistent state is one of the most expensive resources |
3 | Loops and data movement | Repeated work and unnecessary copying can add up quickly |
4 | External calls and execution flow | Calls and repeated calculations increase transaction cost |
5 | Compiler and code-level optimizations | Optimizer settings, constants, immutables, and errors can improve efficiency |
6 | Assembly and advanced techniques | Use only after profiling proves simpler changes are not enough |
Gas Optimization Checklist for Solidity Contracts
Run through this checklist before every release to keep your Solidity gas-efficient code consistent across the project.
Measure
- Baseline First: Record deployment gas and high-frequency function costs before you change a single line of code.
- Track Settings: Write down your compiler configuration and mark the gas-heavy paths that deserve attention first.
Storage
- Trim State: Remove unnecessary persistent data, reduce repeated reads, and skip writes that do not change anything.
- Pack Wisely: Review storage packing and move fixed values into constants or immutables wherever that is clearly appropriate.
Data
- Choose Calldata: Use calldata for read-only external inputs and avoid unnecessary copies of large structs and arrays.
- Consider Transient: Evaluate transient storage for suitable transaction-scoped state on chains that support the Cancun upgrade.
Execution
- Bound Work: Keep loops bounded, avoid duplicate calculations, and cut external calls that add no real value.
- Fail Early: Use custom errors and reject invalid input before any expensive storage writes or computation begins.
Compiler
- Tune Optimizer: Enable the optimizer, benchmark different optimizer runs values, and test via IR against your baseline.
- Stay Current: Use the right EVM target and keep Solidity updated after checking compatibility with your whole toolchain.
Verification
- Prove Results: Run the full test suite and compare gas benchmarks against your original baseline numbers.
- Guard Quality: Review security implications of every change and add gas regression checks to your CI pipeline.
When Should You Stop Optimizing?
Every contract comes to a point where additional savings cost more in risk and effort than they are worth.
Stop when additional gains would require disproportionate code complexity, security risk, audit effort, or maintenance cost. In production contracts, readable and secure code is often better, even if it is marginally more gas-expensive.
Auditors work faster on clear code, which saves money that tiny gas wins rarely recover. When the code is stable, learning to prepare a smart contract for a security audit protects everything you optimized.
Conclusion
Solidity gas optimization works best as a repeatable process rather than a collection of clever tricks. Measure first, find expensive paths, fix architecture and storage, improve execution, tune the compiler, test, then benchmark again. The biggest savings usually come from avoiding unnecessary on-chain work, not from rewriting syntax line by line. Following these Solidity gas optimization best practices keeps smart contract gas optimization practical, honest, and measurable. Teams that optimize Solidity smart contract code this way protect users, budgets, and long-term trust together.
Frequently Asked Questions
What is gas optimization in Solidity?
Gas optimization in Solidity means reducing unnecessary EVM work so transactions cost less without changing any contract behavior or security. It covers storage design, data locations, loops, error handling, and compiler settings, always backed by careful measurement.
How can I reduce gas costs in a Solidity smart contract?
To reduce gas costs, Solidity developers should first measure carefully, then cut unnecessary storage reads and writes everywhere. Next, use calldata, custom errors, constants, and immutables, keep loops bounded, and benchmark your compiler settings carefully.
What operations use the most gas in Solidity?
Persistent storage writes are usually the most expensive, especially when creating brand new state that never existed. Storage reads, external calls, and large calldata on rollups follow, while memory and computation usually cost much less.
Is storage more expensive than memory in Solidity?
Yes, storage is much more expensive because it changes a permanent state that every node must keep forever. Memory is temporary and cleared after each call, so it costs much less for short-lived working data.
Is calldata cheaper than memory?
Calldata can also be cheaper for read-only external input, as it avoids copying into memory. But repeated access can change the trade-off, so the calldata vs memory gas choice depends on the size of the data, how often it will be accessed, and whether the data must change.
Do smaller integer types save gas in Solidity?
Only sometimes, mainly when smaller types let several variables pack into one shared 32-byte storage slot together. As local variables, smaller types can actually cost more because the EVM works natively with 32-byte words.
Are custom errors cheaper than revert strings?
Yes, custom errors are generally cheaper because they encode compactly and avoid storing long message strings inside bytecode. They lower deployment size and revert costs while still giving developers and users clear and descriptive failure reasons.
Does unchecked save gas in Solidity?
Yes, unchecked removes overflow checks and saves gas, but only use it when overflow is truly impossible. Since version 0.8.22, the compiler already applies unchecked increments to many simple loops on its own automatically.
What is transient storage in Solidity?
Transient storage holds data for one transaction only and clears itself automatically when that transaction finishes executing. It suits reentrancy locks and temporary flags, but it requires EIP 1153 support from the Cancun upgrade onward.
Should I use the Solidity optimizer?
Yes, enabling the optimizer is one of the easiest ways to cut both runtime gas and bytecode size. Benchmark different optimizer run values and IR settings, because the best configuration depends on your contract.





