How to Optimize Your Smart Contracts for Ethereum Mainnet Success
Save gas, reduce storage costs, and secure your smart contracts. Learn best practices for optimizing Ethereum code with real Solidity examples.

Smart contracts are the backbone of decentralized apps on Ethereum. Making them efficient isn’t just a nice-to-have; it’s essential. If your contract isn’t optimized, you’ll face exorbitant gas fees and wasted storage, potentially jeopardizing your project before it even starts. Additionally, poorly written code not only increases costs but also exposes you to security risks.
In summary, optimizing your smart contracts reduces expenses, enhances performance, and ensures your project remains secure.
This article provides tips for optimizing Ethereum smart contracts, with a focus on minimizing gas consumption, streamlining logic, and adhering to best practices for secure and maintainable code. These techniques benefit both seasoned developers and newcomers.

1. Write Minimalist Code
When you're coding, keep it simple. Extra lines, functions, or variables can really add up in gas costs, both when you deploy and during use. Get rid of any dead code, unused variables, and repetitive stuff. If you can use a library like OpenZeppelin, go for it. It helps reduce the size of your contract and makes it safer by minimizing potential bugs and vulnerabilities.
Practical Example
Here is an example using a simple setter function.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract GasTest {
uint public x;
// Unoptimized: Unnecessary setter, repeated logic
function setX(uint _x) public {
x = _x;
}
function doubleX() public {
x = x * 2;
}
// Optimized: Combine logic where possible
function setAndDoubleX(uint _x) public {
x = _x * 2;
}
}
Let's run a gas report using forge for this contract.

From the image, you can see that the optimized version costs over 35% less gas for the exact same logic.
From the function costs:
setX
: 43,718 gasdoubleX
: 26,511 gas- Combined: 70,229 gas
setAndDoubleX
: 43,988 gas
CallingsetX
and thendoubleX
means you’re writing to storage twice, which stacks up the costs. The optimized function (setAndDoubleX
) only writes once, making it much cheaper.
2: Choose the Right Data Structures
Using mappings instead of arrays for queries and retrieving values can result in substantial cost savings for both developers and users. Since iterating through arrays onchain is often costly, it's advisable to avoid this practice whenever possible. When structuring your data, consider employing structs to group related information, which enhances clarity and readability.
Practical Example
Here, we have a code that loops through an array to find if an address.
//Looping through an array to find if an address exists
address[] public whitelist;
function isWhitelisted(address user) public view returns (bool) {
for (uint i = 0; i < whitelist.length; i++) {
if (whitelist[i] == user) return true;
}
return false;
}
We would switch it up in this example and ask Claude to optimize the code;

Here is the result;

This optimization dramatically improves gas efficiency. The original code becomes increasingly expensive as the whitelist grows, potentially costing 21,000+ gas for large arrays, while the mapping version maintains a constant ~2,100 gas regardless of size.
The mapping eliminates the need to loop through every address, providing instant results instead of scanning the entire array. This makes the contract much more scalable and cost-effective for production use.
3. Minimize using storage
The single biggest gas guzzler in Ethereum is storage operations. Think of writing to storage on Ethereum like sending postcards. Each time you send a postcard (a storage write), it costs you money and takes time. If you want to send multiple messages (updating several values), it makes more sense to gather all of your thoughts first and send them in one big envelope rather than sending each postcard one by one. This way, you save on postage and effort, just like batching state changes saves on gas costs in Ethereum!
Whenever you need to update several values, do so in memory first and then commit the result to storage in a single operation.
Practical Example
This contract compares Looping and Writing to storage for each user Vs Writing to storage only once.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SimpleStorageGas {
mapping(address => uint256) public balances;
address user1;
address user2;
address user3;
// Set users and initialize balances
function setUpUsers() public {
user1 = address(0x1);
user2 = address(0x2);
user3 = address(0x3);
balances[user1] = 100;
balances[user2] = 100;
balances[user3] = 100;
}
// Unoptimized: Multiple storage writes
function resetAllBalancesUnoptimized() public {
balances[user1] = 0;
balances[user2] = 0;
balances[user3] = 0;
}
// Optimized: Only one storage write
function resetOneBalanceOptimized() public {
balances[user1] = 0;
}
}
Let's run a forge test for this code;

From the result, you can see that the function that resets three user balances (resetAllBalancesUnoptimized
) uses more than 2x the gas compared to resetting just one (resetOneBalanceOptimized
). Every extra write to storage is costly, the more storage slots you update, the more gas you burn.
4. Prioritize Security
Never prioritize gas savings at the expense of security. An optimized contract is meaningless if it exposes user funds to risk. Security must always come first. Adhere to best practices such as the Checks-Effects-Interactions pattern, and use tools like OpenZeppelin’s ReentrancyGuard for any function that handles Ether or sensitive operations. Cutting corners on security will ultimately cost far more than any gas savings you achieve.
Practical example
Here is a contract that uses the Checks-Effects-Interactions pattern vs one that doesn't.

Problem:
If msg.sender
is a contract with a fallback function, it can call withdraw
again before the balance is reduced, draining funds (classic reentrancy attack).

Why is this safe?
If msg.sender
tries to re-enter, their balance is already set to zero (or reduced) before the external call, so a second withdrawal will fail.
Conclusion
Optimizing smart contracts for gas efficiency is crucial, and ensuring their security is equally important. However, these efforts are meaningless if you skip the final step of code verification and auditing. Without onchain auditing, you risk not fully understanding potential vulnerabilities. Even a "gas-efficient" contract can fail catastrophically due to a single critical bug.
Verification serves as public and cryptographic assurance that the deployed code meets the expectations of both developers and users.
This isn’t optional if you care about adoption or security. In the next guide, I’ll break down exactly how to verify your smart contracts using Blockscout, step by step, from compiling your contract to verifying directly from the Blockscout UI, providing you with a transparent link between source code and deployed bytecode.