The Business & Technology Network
Helping Business Interpret and Use Technology
S M T W T F S
 
 
 
 
1
 
2
 
3
 
4
 
5
 
6
 
7
 
8
 
9
 
10
 
11
 
12
 
13
 
14
 
15
 
16
 
17
 
18
 
19
 
20
 
21
 
22
 
23
 
24
 
25
 
26
 
27
 
28
 
29
 
30
 
31
 

5 Gas Optimization Patterns Every Solidity Developer Must Master

DATE POSTED:October 14, 2025
Why Every Byte Counts on the Blockchain

Gas isn’t just some abstract developer thing — it’s actual money. Every transaction you run, every function call you make on Ethereum (or any EVM-based chain) comes with a price. That price? Gas.

Now sure, if you’re new to Solidity, it might not seem like a huge deal at first. But trust me — once your contracts start getting used thousands of times, every little inefficiency turns into real costs. Even something as small as reading a variable too many times can hit your users in the wallet.

This post walks through five simple-but-powerful ways to save gas. And no, they’re not magic tricks — just smart coding habits that pay off over time.

The Foundation: Understanding Gas Mechanics

Alright, quick refresher before we jump in. If you want to optimize gas, you need to know where it’s going.

Here’s the breakdown:

  • Storage (Persistent Data): This is your contract’s hard disk. New writes cost around 20,000 gas. Just updating something? That’s about 5,000. Still not cheap.
  • Memory (Temporary Data): Like RAM. It’s cleared after function execution and much cheaper than storage — but if you use a lot of it, it adds up.
  • Calldata (Read-Only Input): Hands down the cheapest option. It stores input data from external calls and doesn’t let you modify it.
TL;DR: Storage costs hurt the most. Avoid it unless you absolutely need it.5 Gas Optimization PatternsPattern 1: Storage Packing (Yeah, Like Tetris)

The EVM likes to store things in 32-byte slots. That’s 256 bits per slot. So, if you’re declaring a bunch of small variables one after another — like bool, uint8, etc.—and spacing them out in the contract, Solidity might just throw each one into its own full slot. Wasteful, right?

The Fix: Pack smaller variables together so they sit side-by-side in one slot. You’ll instantly save gas, especially on deployment.

// Inefficient - Uses 3 separate storage slots.
contract InefficientPacking {
uint256 public largeData; // Slot 1
uint8 public status; // Slot 2 (wastes 31 bytes)
bool public isActive; // Slot 3 (wastes 31 bytes)
}
// Optimized - All smaller variables fit into a single slot.
contract OptimizedPacking {
uint128 public tokenBalance;
uint128 public feeAmount;
uint8 public status;
bool public isActive;
bool public isApproved;
}Pattern 2: Cache Storage Reads in Memory

This one’s pretty straightforward, and yet a lot of folks miss it.

Here’s the thing: reading from storage isn’t free. If you’re doing it multiple times inside a single function, guess what? You’re paying for each read — even if it’s the exact same value.

The Fix: Pull the value into a local variable once. Do your work there. Then write it back to storage (if needed) at the end.

// Inefficient - Reads 'userPoints[msg.sender]' from storage three times.
function updatePoints(uint256 reward) public {
userPoints[msg.sender] = userPoints[msg.sender].add(reward); // Read 1, Write 1
if (userPoints[msg.sender] > MAX_POINTS) { // Read 2
userPoints[msg.sender] = MAX_POINTS; // Write 2
}
}
// Optimized - Reads from storage once, writes to storage once.
function updatePointsOptimized(uint256 reward) public {
uint256 currentPoints = userPoints[msg.sender];
currentPoints = currentPoints.add(reward);
if (currentPoints > MAX_POINTS) {
currentPoints = MAX_POINTS;
}
userPoints[msg.sender] = currentPoints;
}Pattern 3: Use calldata (Don’t Just Default to memory)

Ever noticed how Solidity makes you choose between memory and calldata for function parameters like arrays or strings? A lot of devs just default to memory because it feels familiar.

But here’s the catch: when you use memory, Solidity makes a full copy of the input. And yep—copying data costs gas.

The Fix: If you’re not modifying the input, use calldata. It avoids the copy step and keeps things lean.

// Inefficient - Uses 'memory'. The array is copied from calldata into memory.
function verifyUsers(address[] publicKeys) public view returns (bool) {
// ... logic that only reads 'publicKeys'
}
// Optimized - Uses 'calldata'. The array is read directly from the transaction input.
function verifyUsersOptimized(address[] calldata publicKeys) public view returns (bool) {
for (uint i = 0; i < publicKeys.length; i++) {
require(publicKeys[i] != address(0), "Invalid address");
}
return true;
}Pattern 4: Short-Circuit Conditionals (Put the Cheap Stuff First)

This one’s sneaky. Let’s say you’ve got a require statement with two conditions. One is quick and easy—like checking a local variable. The other is a heavy function call (maybe signature verification or an external lookup).

If you put the expensive one first, it gets executed even if the cheap one fails.

The Fix: Put the least expensive (or most likely to fail) condition first.

// Inefficient - Expensive check runs even if the cheaper one would fail.
function accessRestrictedResource() public {
require(verifySignature(msg.sender) && isWhitelisted(msg.sender), "Access denied");
}
// Optimized - The cheap check runs first, skipping the expensive one if it fails.
function accessRestrictedResourceOptimized() public {
require(isWhitelisted(msg.sender) && verifySignature(msg.sender), "Access denied");
}Quick tip: Same thing applies to ||. If you expect the first condition to succeed, put it first. Saves gas if the second one is expensive.Pattern 5: Use constant and immutable Wisely

Got a value that never changes? Like a max supply or owner address?

If you declare it as a regular state variable, Solidity puts it in storage. And every time you access it, you’re burning gas — over and over again.

The Fix: Use constant for compile-time values. Use immutable for values that are set once at deployment. Both are much cheaper at runtime.

// Inefficient - These values are read from expensive storage slots every time they're used.
contract InefficientConstants {
uint256 public MAX_SUPPLY = 100000000;
address public OWNER_ADDRESS;
constructor(address _owner) {
OWNER_ADDRESS = _owner;
}
}
// Optimized - Values are baked into the contract bytecode, saving runtime gas.
contract OptimizedConstants {
uint256 public constant MAX_SUPPLY = 100000000;
address public immutable i_owner;
constructor(address _owner) {
i_owner = _owner;
}
function getOwner() public view returns (address) {
return i_owner;
}
}Security Considerations (Don’t Trade Safety for Speed)

Here’s the thing: gas optimization is great — until it isn’t. If you’re chasing micro-savings at the cost of security or clarity, you’re heading into dangerous territory.

  • Readable > Fast (Sometimes): If an auditor can’t read your logic easily, that’s a red flag. Saving 10 gas isn’t worth the confusion.
  • Unchecked Math: Sure, unchecked saves gas by disabling overflow checks—but only use it when you’ve proven it’s safe.
  • Overpacking Variables: Yeah, you can cram multiple bits into a single slot. But if you’re doing wild bitwise operations just to save a few gas units, ask yourself: is this code still safe? Maintainable? Auditable?
Bonus: Use the Compiler Optimizer

Solidity’s compiler has a built-in optimizer that can shrink your bytecode and improve gas performance.

  • How It Works: It simplifies expressions, inlines functions, and eliminates redundancy.
  • The runs Setting:
  • runs=1: Optimizes for cheaper deployment
  • runs=20000: Optimizes for cheaper execution (runtime gas)

If your contract’s going to be used heavily — like in a token or protocol — go with 20000. It costs more to deploy, but saves users money on every interaction.

Conclusion

Writing smart contracts that just “work” isn’t enough anymore. You’ve got to think about gas — because your users definitely will. With just a few smart choices, you can make your contracts cheaper to run and friendlier to the people using them.

These five patterns are a great starting point. Use gas reporters like Hardhat or Foundry to measure improvements. Keep your code clear, safe, and optimized.

Final reminder: Readable code wins. Safe code wins. And then — optimize.

Have questions or ideas?
Contact us at: [email protected]
explore more:
www.ancilar.com

5 Gas Optimization Patterns Every Solidity Developer Must Master was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.