Back to Blog
Solidity Gas Optimization Tips With Assembly You Haven't Heard Yet!
SolidityWeb3 Security

Solidity Gas Optimization Tips With Assembly You Haven't Heard Yet!

5 min
When it comes to writing efficient and cost-effective smart contracts on the Ethereum blockchain, every little bit of gas savings counts. One way to optimize gas usage is by using assembly code instead of Solidity in certain parts of your contract.
In this article, we will explore how using assembly code can help you save gas in various scenarios, from hashing and math operations to writing to storage and checking for zero addresses. We will provide examples of Solidity code and their corresponding assembly implementations, along with gas usage comparisons so you can see for yourself the potential gas savings.

1. Use assembly to hash instead of Solidity

1function solidityHash(uint256 a, uint256 b) public view {
2 //unoptimized
3 keccak256(abi.encodePacked(a, b));
4}
Gas: 313
1function assemblyHash(uint256 a, uint256 b) public view {
2 //optimized
3 assembly {
4 mstore(0x00, a)
5 mstore(0x20, b)
6 let hashedVal := keccak256(0x00, 0x40)
7 }
8}
Gas: 231
Let's take a look at the Yul instructions used and their explanation.
Yul hash instructions

2. When possible, use assembly instead of unchecked{++i}

You can also use unchecked{++i;} for even more gas savings but this will not check to see if i overflows. For best gas savings, use inline assembly, however, this limits the functionality you can achieve.
1//loop with unchecked{++i}
2function uncheckedPlusPlusI() public pure {
3 uint256 j = 0;
4 for (uint256 i; i < 10; ) {
5 j++;
6 unchecked {
7 ++i;
8 }
9 }
10}
Gas: 1329
1//loop with inline assembly
2function inlineAssemblyLoop() public pure {
3 assembly {
4 let j := 0
5 for {
6 let i := 0
7 } lt(i, 10) {
8 i := add(i, 0x01)
9 } {
10 j := add(j, 0x01)
11 }
12 }
13}
Gas: 709
Now, the explanation of lt Yul instruction and similar comparison opcodes.
Yul comparison instructions

3. Use assembly for math (add, sub, mul, div)

Use assembly for math instead of Solidity. You can check for overflow/underflow in assembly to ensure safety.

Addition

1//addition in Solidity
2function addTest(uint256 a, uint256 b) public pure {
3 uint256 c = a + b;
4}
Gas: 303
1//addition in assembly
2function addAssemblyTest(uint256 a, uint256 b) public pure {
3 assembly {
4 let c := add(a, b)
5 if lt(c, a) {
6 mstore(0x00, "overflow")
7 revert(0x00, 0x20)
8 }
9 }
10}
Gas: 263

Subtraction

1//subtraction in Solidity
2function subTest(uint256 a, uint256 b) public pure {
3 uint256 c = a - b;
4}
Gas: 300
1//subtraction in assembly
2function subAssemblyTest(uint256 a, uint256 b) public pure {
3 assembly {
4 let c := sub(a, b)
5 if gt(c, a) {
6 mstore(0x00, "underflow")
7 revert(0x00, 0x20)
8 }
9 }
10}
Gas: 263

Multiplication

1//multiplication in Solidity
2function mulTest(uint256 a, uint256 b) public pure {
3 uint256 c = a * b;
4}
Gas: 325
1//multiplication in assembly
2function mulAssemblyTest(uint256 a, uint256 b) public pure {
3 assembly {
4 let c := mul(a, b)
5 if lt(c, a) {
6 mstore(0x00, "overflow")
7 revert(0x00, 0x20)
8 }
9 }
10}

Are you audit-ready?

Download the free Pre-Audit Readiness Checklist used by 40+ protocols preparing for their first audit.

No spam. Unsubscribe anytime.

Gas: 265

Division

1//division in Solidity
2function divTest(uint256 a, uint256 b) public pure {
3 uint256 c = a * b;
4}
Gas: 325
1//division in assembly
2function divAssemblyTest(uint256 a, uint256 b) public pure {
3 assembly {
4 let c := div(a, b)
5 if gt(c, a) {
6 mstore(0x00, "underflow")
7 revert(0x00, 0x20)
8 }
9 }
10}
Gas: 265
So, now let's see the explanation of these Yul instructions.
Yul storage instructions

4. Use assembly to write storage values

1address owner = 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84;
2
3function updateOwner(address newOwner) public {
4 owner = newOwner;
5}
Gas: 5302
1address owner = 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84;
2
3function assemblyUpdateOwner(address newOwner) public {
4 assembly {
5 sstore(owner.slot, newOwner)
6 }
7}
Gas: 5236
We've seen some of these Yul instructions above but let's remind ourselves of their explanation.
Yul zero check instructions

5. Use assembly to check for address(0)

1function ownerNotZero(address _addr) public pure {
2 require(_addr != address(0), "zero address)");
3}
Gas: 258
1function assemblyOwnerNotZero(address _addr) public pure {
2 assembly {
3 if iszero(_addr) {
4 mstore(0x00, "zero address")
5 revert(0x00, 0x20)
6 }
7 }
8}
Gas: 252
iszero and mstore we've already seen above, so let's check now what is the explanation for revert.
Yul balance instructions

6. Use assembly when getting a contract's balance of ETH

You can use selfbalance() instead of address(this).balance when getting your contract's balance of ETH to save gas.
1function addressInternalBalance() public returns (uint256) {
2 return address(this).balance;
3}
Gas: 148
1function assemblyInternalBalance() public returns (uint256) {
2 assembly {
3 let c := selfbalance()
4 mstore(0x00, c)
5 return(0x00, 0x20)
6 }
7}
Gas: 133
return we have just seen above, so let's check now selfbalance.
Smart contract hacking course

Get in touch

At Zealynx, we specialize in smart contract security audits — including gas optimization reviews that go beyond surface-level checks. Whether you need a full protocol audit or a targeted gas efficiency review, our team is ready to help you ship secure, cost-effective code. Reach out to start the conversation.
Want to stay ahead with more in-depth analyses like this? Subscribe to our newsletter and ensure you don't miss out on future insights.

Are you audit-ready?

Download the free Pre-Audit Readiness Checklist used by 40+ protocols preparing for their first audit.

No spam. Unsubscribe anytime.

oog
zealynx

Smart Contract Security Digest

Monthly exploit breakdowns, audit checklists, and DeFi security research — straight to your inbox

© 2026 Zealynx