# Understanding Gas in Ethereum — How It Actually Works
Gas confuses every beginner. "Why did I pay $50 for a failed transaction?" "What's the difference between gas price and gas limit?" This guide explains Ethereum gas from first principles.
What is Gas?
Gas is the computational effort required to execute operations on Ethereum.
Think of it like gasoline for your car:
- Gas units = liters of gasoline
- Gas price = price per liter
- Total cost = units × price
In Ethereum:
- Gas units = computational steps
- Gas price = ETH per unit
- Total cost = gas used × gas price
Why Does Gas Exist?
Two reasons:
1. Prevent Spam
Without gas, someone could deploy this:
contract InfiniteLoop {
function attack() public {
while (true) {} // Would freeze the network forever
}
}
Gas limits execution time. When gas runs out, execution stops.
2. Compensate Validators
Validators include your transaction in a block. They deserve payment for:
- Running the computation
- Storing state changes
- Bandwidth costs
Gas Units vs ETH
Gas units measure computational work. They're the same regardless of ETH price.
Example operations:
| Operation | Gas Cost |
|-----------|----------|
| Addition (a + b) | 3 |
| Multiplication | 5 |
| SSTORE (write to storage) | 20,000 (cold) / 5,000 (warm) |
| SLOAD (read from storage) | 2,100 (cold) / 100 (warm) |
| Transfer ETH | 21,000 |
| Deploy contract | 32,000 + code size |
A simple transfer() uses ~21,000 gas. A complex DeFi swap might use 200,000 gas.
EIP-1559: The London Upgrade (2021)
Before EIP-1559, you set one gas price. Now there are TWO components:
Total Fee = (Base Fee + Priority Fee) × Gas Used
Base Fee
- Determined by network demand, not users
- Burned (removed from circulation)
- Adjusts every block based on congestion:
- Block > 50% full → base fee increases 12.5%
- Block < 50% full → base fee decreases 12.5%
Currently ~10-50 gwei depending on congestion.
Priority Fee (Tip)
- Set by users to incentivize validators
- Paid to validators (not burned)
- Higher tip = faster inclusion
Typically 1-2 gwei in normal times.
Gas Limit vs Gas Used
Gas Limit
The maximum gas you authorize.
// You set: Gas Limit = 100,000
// Actual execution uses: 67,000
// You pay for: 67,000 (not 100,000)
// Refund: 33,000 worth of gas
If execution needs 120,000 but your limit is 100,000:
- Transaction fails (reverts)
- You still pay for the 100,000 gas attempted
- 🔥 This is why failed transactions cost money
Best practice: Set limit 20-50% higher than estimate.
Gas Used
The actual amount consumed.
function transfer(address to, uint256 amount) external {
balances[msg.sender] -= amount; // SSTORE: 5,000 gas
balances[to] += amount; // SSTORE: 20,000 gas
// Total: ~25,000 + overhead
}
You only pay for gas actually used (up to your limit).
Calculating Transaction Cost
Before EIP-1559 (Legacy)
Cost = Gas Used × Gas Price
Example:
100,000 gas × 50 gwei = 5,000,000 gwei = 0.005 ETH
After EIP-1559
Max Fee Per Gas = Base Fee + Max Priority Fee
Cost = Gas Used × (Base Fee + Priority Fee)
Example:
Base Fee: 30 gwei
Priority Fee: 2 gwei
Gas Used: 100,000
Cost = 100,000 × 32 gwei = 3,200,000 gwei = 0.0032 ETH
Setting Fees in Wallets
You specify:
- Max Fee Per Gas: Absolute maximum you'll pay (e.g., 100 gwei)
- Max Priority Fee: Maximum tip to validator (e.g., 2 gwei)
If base fee is 30 gwei and you set max fee 100 gwei:
- Actual fee = 30 + 2 = 32 gwei
- You save 68 gwei per gas unit
- Wallet refunds the difference
Block Gas Limit
Each block has a maximum total gas (currently 30 million).
Block Gas Limit: 30,000,000
Average Transaction: 100,000 gas
Transactions per block: ~300
If demand exceeds block space → base fee increases → fewer transactions → congestion clears.
Common Operation Costs
Storage Operations (Most Expensive)
contract Storage {
uint256 public value; // First write: 20,000 gas
function set(uint256 newValue) external {
value = newValue; // Update: 5,000 gas
}
function get() external view returns (uint256) {
return value; // Read: 2,100 gas (first time), 100 gas (subsequent)
}
}
Cold access (first time in a transaction): expensive
Warm access (subsequent times): cheap
ERC-20 Transfer
function transfer(address to, uint256 amount) external {
// 2 SSTORE operations + overhead
// Total: ~45,000-65,000 gas
}
Uniswap Swap
router.swapExactTokensForTokens(...);
// Multiple contracts, complex math, storage updates
// Total: ~150,000-250,000 gas
NFT Mint
nft.mint();
// Create new token, update mappings, emit event
// Total: ~50,000-150,000 gas
How to Estimate Gas
Method 1: Wallet Simulation
Wallets (MetaMask, Rainbow) simulate the transaction and estimate gas.
Method 2: eth_estimateGas RPC
const provider = new ethers.JsonRpcProvider("https://...");
const gasEstimate = await provider.estimateGas({
to: contractAddress,
data: encodedFunctionCall,
from: userAddress,
});
console.log(Estimated gas: ${gasEstimate});
Method 3: Foundry Gas Reports
forge test --gas-report
| Function | Gas |
|-----------|---------|
| mint | 51,234 |
| transfer | 34,567 |
How to Save Gas
1. Use calldata Instead of memory
// ❌ Expensive
function process(uint256[] memory data) external {
// Copies to memory: costs gas
}
// ✅ Cheap
function process(uint256[] calldata data) external {
// Reads directly from transaction data
}
Savings: ~50 gas per array element.
2. Pack Storage Variables
// ❌ Uses 3 storage slots (3 × 20,000 gas)
contract Inefficient {
uint128 a;
uint256 b;
uint128 c;
}
// ✅ Uses 2 storage slots (2 × 20,000 gas)
contract Efficient {
uint128 a;
uint128 c;
uint256 b;
}
Each slot is 32 bytes. Pack small variables together.
3. Use Events Instead of Storage
// ❌ Storage: 20,000 gas
mapping(uint256 => string) public messages;
function log(uint256 id, string memory message) external {
messages[id] = message;
}
// ✅ Events: ~375 gas
event Message(uint256 indexed id, string message);
function log(uint256 id, string memory message) external {
emit Message(id, message);
}
Use events for data you don't need to query on-chain.
4. Batch Operations
// ❌ Multiple transactions
token.transfer(user1, 100);
token.transfer(user2, 100);
token.transfer(user3, 100);
// 3 × 21,000 base cost = 63,000 gas wasted
// ✅ Single transaction
token.batchTransfer([user1, user2, user3], [100, 100, 100]);
// 1 × 21,000 base cost
5. Use unchecked for Safe Math
// ❌ Overflow checks on every operation
for (uint i = 0; i < 1000; i++) {
// i++ includes overflow check
}
// ✅ Skip checks when overflow impossible
for (uint i = 0; i < 1000;) {
// ... logic
unchecked { i++; }
}
Savings: ~20 gas per operation.
6. Short-Circuit Conditionals
// ❌ Evaluates both conditions
if (expensiveCheck() && cheapCheck()) {}
// ✅ Short-circuits if first is false
if (cheapCheck() && expensiveCheck()) {}
maxFeePerGas vs maxPriorityFeePerGas
When sending a transaction:
await contract.mint({
maxFeePerGas: ethers.parseUnits("100", "gwei"), // Max total
maxPriorityFeePerGas: ethers.parseUnits("2", "gwei"), // Max tip
});
Scenario 1: Normal Congestion
Base Fee: 30 gwei
Your Max Fee: 100 gwei
Your Max Priority: 2 gwei
Actual Fee Paid: 30 + 2 = 32 gwei
Refund: 68 gwei per gas unit
Scenario 2: High Congestion
Base Fee: 95 gwei
Your Max Fee: 100 gwei
Your Max Priority: 2 gwei
Actual Fee Paid: 95 + 2 = 97 gwei
Refund: 3 gwei per gas unit
Scenario 3: Extreme Congestion
Base Fee: 120 gwei
Your Max Fee: 100 gwei
Result: Transaction NOT included (max fee too low)
Gas Tokens (Deprecated)
Before EIP-1559, "gas tokens" (CHI, GST2) exploited storage refunds to save gas. They're obsolete now.
Layer 2 Solutions
L2s (Arbitrum, Optimism, Base) reduce gas by 10-100x:
| Network | Transfer Cost |
|---------|---------------|
| Ethereum | 0.001 ETH ($3) |
| Arbitrum | 0.0001 ETH ($0.30) |
| Optimism | 0.0001 ETH ($0.30) |
| Base | 0.00005 ETH ($0.15) |
Same code, cheaper execution.
Common Questions
"Why did my failed transaction cost gas?"
Validators still executed your code (until it failed). They deserve payment for that work.
"Why is gas so high right now?"
High demand → blocks full → base fee increases. Check etherscan.io/gastracker for current prices.
"Should I set max fee very high?"
No need. You only pay baseFee + priorityFee, excess is refunded. But set it high enough to cover potential base fee increases.
"How do I get instant inclusion?"
Set a high priority fee (5-10 gwei). Validators prioritize high tips.
Summary
- Gas units = computational work (constant)
- Gas price = ETH cost per unit (variable)
- Base fee = burned, adjusts with congestion
- Priority fee = tip to validators
- Gas limit = max you authorize (you're refunded unused)
- Gas used = actual amount consumed
Pro tip: Use gwei.io or ultrasound.money to check current gas prices before transacting.
Next: Learn gas optimization techniques to write efficient smart contracts!