Ethereum smart contract development is as much about functionality as it is about efficiency. With every operation on the blockchain incurring gas costs, optimizing your code isn’t just a best practice—it’s essential for reducing transaction fees and improving user experience. This guide dives into advanced techniques to minimize gas consumption in Solidity contracts, focusing on storage layout, function design, and opcode-level efficiencies.
Whether you're building DeFi protocols, NFT marketplaces, or Web3 applications, these strategies will help you write leaner, faster, and more cost-effective smart contracts.
Optimize Variable Ordering to Reduce Storage Costs
One of the most impactful yet often overlooked gas-saving techniques involves how you declare state variables in your contract.
Ethereum organizes storage in 32-byte slots. The EVM packs variables into the same slot whenever possible, but only if they fit. If variables are declared inefficiently, you may end up using extra slots—each additional SSTORE operation increases gas usage.
Efficient Packing Example
contract MyContract {
uint64 public a;
uint64 public b;
uint64 public c;
uint64 public d;
function test() public {
a = 1;
b = 2;
c = 3;
d = 4;
}
}Here, four uint64 values (each 8 bytes) fit perfectly into one 32-byte slot. Assigning all four consumes minimal gas since only one SSTORE is needed.
Inefficient Packing Due to Poor Ordering
contract MyContract {
uint64 public a;
uint64 public b;
bytes1 public e; // Only 1 byte
uint64 public c;
uint64 public d;
function test() public {
a = 1;
b = 2;
c = 3;
d = 4;
}
}Even though e uses just 1 byte, its placement breaks contiguous packing. Now:
a,b→ Slot 1 (16 bytes used)e→ starts at Slot 2 (but wastes 31 bytes)c,d→ must go to Slot 3
👉 Discover how efficient coding can save real costs on blockchain transactions.
This forces two SSTORE operations, increasing gas significantly. To avoid this, group variables by type and size, placing smaller ones together and larger ones sequentially.
Best Practice: Declare variables from largest to smallest (uint256,uint128, down tobool,bytes1) and keep similar types together.
Use Bit Packing for Flags in Function Parameters
Function parameters contribute directly to calldata costs. The formula for gas cost is:
n * 68 + (32 - n) * 4 // where n = number of non-zero bytesYou can reduce this by packing metadata or flags into unused bits of existing parameters.
Real-World Example: Reusing Address Bits
An address type uses only 20 bytes. That leaves 12 leading bytes unused in a 32-byte word. Instead of passing an extra boolean flag, encode it in those high-order bits.
// Encode: top 8 bits = flag, lower 20 bytes = address
function processPacked(uint256 packedData) external {
bool flag = (packedData >> (8 * 24)) > 0; // Extract first byte
address addr = address(uint160(packedData));
if (flag) {
// Handle special case
}
}This avoids adding a separate parameter, reducing calldata size and saving gas—especially valuable in batch operations.
👉 Learn how data compression techniques cut gas fees instantly.
Prefer keccak256 Over Other Hash Functions
When hashing data in Solidity, choose wisely. Different hash functions have vastly different gas costs:
| Hash Function | Approximate Gas Cost |
|---|---|
keccak256 | ~30 + per-word cost |
sha256 | ~60 + per-word cost |
ripemd160 | ~600 + fixed cost |
✅ Always prefer keccak256 unless interoperability requires another standard.
Also, minimize input size: fewer arguments and smaller data mean lower gas. For example:
keccak256(abi.encode(a, b)); // Good
keccak256(abi.encodePacked(a, b)); // Even better (no padding)Use abi.encodePacked when alignment isn’t required—it reduces word count and thus gas.
Leverage Shortcut Evaluation in Logic Gates
Solidity supports short-circuit evaluation with && and ||. Use this to put cheaper checks first.
Logical OR: Check Cheap Condition First
if (isTrustedSender(sender) || expensiveValidation(hash)) {
// Proceed
}If isTrustedSender returns true, expensiveValidation won’t run—saving significant gas.
Logical AND: Fail Fast with Inexpensive Checks
if (quickSanityCheck(input) && complexComputation(data)) {
// Proceed
}Place lightweight validations like bounds checking or zero-address guards upfront.
This pattern is crucial in access control, validation pipelines, and conditional logic trees.
Use external Functions with calldata Parameters
For functions called externally (including from wallets or other contracts), mark them as external and use calldata instead of memory.
Why It Matters
Public functions automatically copy inputs to memory—a costly operation. External functions can read directly from calldata without copying.
// Costly: copies _data to memory
function bad(bytes memory _data) public { ... }
// Efficient: reads directly from calldata
function good(bytes calldata _data) external { ... }Even better: if the function doesn’t modify inputs, calldata avoids duplication entirely.
Note: For internal calls, usememory. But for external interfaces (like most user-facing functions), preferexternal.
Minimize External Contract Calls
Inter-contract calls (via .call, .delegatecall, or interface calls) are expensive due to context switching and security overhead.
Best Strategy: Batch Results
Instead of:
value1 = other.getValue1();
value2 = other.getValue2();
value3 = other.getValue3();Design the target contract to return multiple values at once:
(values1, value2, value3) = other.getValues();This reduces:
- Number of external jumps
- Calldata encoding/decoding overhead
- Reentrancy guard checks (if used)
👉 See how batching transactions slashes gas bills on-chain.
Additionally, consider caching frequently accessed values locally (with proper update mechanisms) to avoid repeated calls.
Free Storage to Claim Gas Refunds
Ethereum rewards gas when you clear storage entries using delete.
The refund is up to 4,800 gas per zeroed slot, which can offset other costs—especially in complex transactions.
Example: Clearing a Mapping Entry
mapping(address => uint256) public balances;
function withdrawAndClear() external {
uint256 amount = balances[msg.sender];
delete balances[msg.sender]; // Triggers refund
payable(msg.sender).transfer(amount);
}⚠️ Refunds are applied at the end of the transaction and don’t reduce upfront cost. They also have a cap (~50% of total gas used), so don’t rely on them for budgeting.
Nonetheless, freeing unused storage improves network efficiency and slightly lowers net cost—ideal for cleanup routines or state resets.
Frequently Asked Questions (FAQ)
Q: Does variable ordering matter if all are uint256?
A: No. Each uint256 takes exactly one full slot (32 bytes), so packing doesn't apply. But ordering still affects readability and future extensibility.
Q: Can I pack a bool and multiple uint8s efficiently?
A: Yes! Up to 32 bool or small integers can share one slot if declared consecutively. Example:
bool flag1;
bool flag2;
uint8 mode;
// These pack tightly into one slotQ: Is keccak256 safe for cryptographic purposes?
A: Yes. Despite being cheaper, keccak256 is cryptographically secure and the standard hash function in Ethereum.
Q: Why not always use external instead of public?
A: Because external functions can't be called internally. If you need internal reuse (e.g., library-style logic), use public. Otherwise, default to external.
Q: Do gas refunds make deleting storage always beneficial?
A: Not necessarily. The refund only applies post-execution and is capped. Use deletion for genuine state cleanup—not just for refunds.
Q: Can I optimize events for gas too?
A: Yes! Use indexed parameters sparingly (they go into bloom filters), prefer fixed-size types, and emit only essential data.
By applying these advanced patterns—strategic variable layout, efficient parameter encoding, optimized logic flow, and thoughtful external interactions—you’ll significantly reduce gas costs across your Ethereum applications.
Remember: every byte saved compounds across thousands of users and transactions. Efficiency isn't optional—it's foundational to scalable blockchain development.
Core Keywords: Ethereum gas optimization, Solidity gas tips, reduce SSTORE cost, keccak256 vs sha256, calldata vs memory, external function modifier, shortcut evaluation, storage packing