Why gas exists: a meter against the infinite loop
Picture the smallest possible attack on Ethereum: a contract whose body is just `while (true) {}`. Send one transaction that calls it. Now remember what the EVM actually is — not one computer, but every full node on Earth re-executing the same code to agree on the result. Without a brake, that single loop would spin forever on thousands of machines at once. The chain would simply stop producing blocks. One spammer, with one transaction, would have taken the whole network down.
Deciding in advance whether an arbitrary program halts is the famous halting problem — provably impossible in general. Ethereum's design does not try to solve it. It sidesteps it: charge for every single step of work, give each transaction a finite budget, and stop the moment the budget runs out. That budget is denominated in gas, and the rule that bills each step is gas metering. The infinite loop is no longer dangerous — it just burns through its budget and gets cut off.
So gas is fundamentally a denial-of-service defense. It makes computation cost money in proportion to how much work it demands, which is exactly why a griefing contract can't run for free. Everything else — fee markets, optimization, the price you pay to swap a token — is built on top of this one idea: *work is metered, and metered work must be paid for.*
Every opcode carries a price tag
You met the EVM last rung as a stack machine that executes one opcode at a time. The gas system bolts a fixed price onto each of those opcodes, defined in Ethereum's Yellow Paper. Cheap arithmetic on the stack costs a few gas; touching permanent storage costs thousands. Here is a representative slice of the schedule (post-Berlin, with the EIP-2929 warm/cold rule):
Opcode Gas Note ------------------------------------------------------- STOP 0 halts; costs nothing ADD / SUB 3 G_verylow (cheap math) MUL 5 G_low PUSH1 .. PUSH32 3 push a constant onto the stack POP 2 G_base MLOAD / MSTORE 3 + memory-expansion cost KECCAK256 30 + 6 per 32-byte word hashed SLOAD (cold) 2100 FIRST read of a storage slot SLOAD (warm) 100 slot already touched this tx SSTORE 0 -> X 22100 write a FRESH slot (20000 + 2100 cold) SSTORE X -> Y 5000 overwrite an existing non-zero slot LOG1 750+ 375 base + 375/topic + 8/byte of data CALL 100+ warm; +9000 to send ETH; +25000 new acct CREATE 32000 deploy a new contract
Two refinements matter. First, warm vs cold (EIP-2929, the Berlin fork): the first time a transaction reads a storage slot or an account in a block it pays a high "cold" price; every later touch of the same slot is cheap and "warm." The protocol charges you for pulling fresh data off disk, not for re-reading what's already in the node's cache. Second, memory grows quadratically — see the gotcha below.
A worked example: adding up the bill
Let's price a tiny transaction end to end. The contract just computes `3 + 5` and stores the result in a state variable that was previously zero. Two costs sit outside the opcodes: a flat intrinsic cost of 21,000 gas that every transaction pays (it covers signature recovery, nonce checks, and base overhead), plus calldata at 4 gas per zero byte and 16 gas per non-zero byte for the input you send. Now add the opcodes:
// Solidity: function set() external { stored = 3 + 5; } // stored was 0
Transaction intrinsic cost ........................ 21000
PUSH1 0x03 (push 3) ............................ 3
PUSH1 0x05 (push 5) ............................ 3
ADD (3 + 5 = 8 on the stack) ........... 3
PUSH1 0x00 (push storage slot 0) .............. 3
SSTORE (slot 0: 0 -> 8, a FRESH write) ..... 22100
--------------------------------------------------------
TOTAL ............................................. 43112 gas
// The single SSTORE is 22100 / 43112 = 51% of the entire bill.
// The actual arithmetic (ADD) is 3 gas — 0.007% of the cost.The lesson is blunt: in the EVM, *computation is cheap and remembering is expensive.* Doing the math costs 3 gas; persisting one 32-byte word into the world's shared state costs 22,100. That ratio shapes how every serious contract is written, and it's why the rest of this guide keeps coming back to storage.
Gas limit, running out, and who pays anyway
Every transaction you sign carries a gas limit — the maximum units of work you authorize. The wallet estimates how much your call should need (the 43,112 above) and pads it. If execution stays under the limit, you're charged only for the gas actually consumed and the unused allowance is never spent. So far, intuitive.
Now the counterintuitive part. If execution hits the gas limit before finishing, the EVM throws an out-of-gas exception and reverts every state change the transaction made — as if it never ran. But you still pay for all the gas it burned getting there. State is rolled back; the fee is not refunded. This feels unfair until you remember why gas exists: thousands of nodes really did perform that work, and someone has to compensate them — otherwise the free out-of-gas revert would itself become the DoS vector.
There's a second, macro-level cap: the block gas limit. A whole block can only hold so much gas worth of transactions (a target of 15 million gas, stretchable to 30 million — more on that next). This is the network-wide DoS bound: it guarantees that no single block can demand more computation than honest nodes can re-execute in the time between blocks, keeping verification within reach of ordinary hardware.
EIP-1559: base fee, tip, and the burn
We've priced the work; now the price per unit. Before August 2021, Ethereum ran a naïve first-price auction: everyone guessed a `gasPrice`, the highest bidders got in, and the block producer pocketed it all. Users routinely overpaid, fees swung wildly, and wallets had to play oracle. EIP-1559 (the London fork) replaced that with a transparent, mostly-predictable market.
Under EIP-1559, each block has a protocol-set base fee per gas. You don't bid it — it's computed. After a block, the base fee adjusts by at most ±12.5% depending on how full the previous block was relative to the 15M-gas target: a full (30M) block pushes it up 12.5%, an empty one pulls it down 12.5%, a half-full block leaves it flat. Crucially, the base fee is burned — destroyed, paid to no one. On top of it you add a priority fee (a tip) that does go to the block proposer as the incentive to include you.
// You set: maxPriorityFeePerGas = 2 gwei (tip to proposer)
// maxFeePerGas = 30 gwei (your hard ceiling)
// Network: base fee = 20 gwei (burned)
// Your tx uses 43112 gas (the example above).
effective price = min(maxFee, baseFee + priorityFee)
= min(30, 20 + 2) = 22 gwei
total fee = 43112 * 22 gwei = 948,464 gwei ~= 0.00095 ETH
burned = 43112 * 20 gwei = 862,240 gwei (removed from supply)
to proposer = 43112 * 2 gwei = 86,224 gwei (the tip)
refunded to you = (maxFee - effective) * gas
= (30 - 22) * 43112 gwei (you escrow maxFee, pay less)Two honest consequences. The base fee makes fees predictable — you can see next block's floor and set `maxFeePerGas` with confidence, instead of bidding blind. And burning the base fee removes ether from supply; when network usage is high, more ether is burned than issued to validators, making ether net-deflationary — but only when usage is high. In quiet periods issuance outruns the burn and supply still grows. It is a usage-dependent pressure, not a guarantee.
Why storage dominates — and how to pay less
Come back to that 700x gap. Why is `SSTORE` so brutally expensive when `ADD` is nearly free? Because storage isn't scratch space — it's a permanent entry in the world state that every node must keep, replicate, and prove forever. A transient calculation evaporates the instant the call ends; a stored word is a liability the entire network shoulders for the life of the chain. Gas prices that asymmetry honestly: you pay once, up front, for an unbounded future cost you're imposing on everyone.
There's a flip side: freeing storage earns a refund. Clearing a slot back to zero (`SSTORE X -> 0`) refunds gas, because you're shrinking the state everyone carries. But the refund is capped — at most 20% of the transaction's total gas (EIP-3529). That cap exists for a real reason: traders once minted "gas tokens" (GST2, CHI) by writing junk storage when gas was cheap and clearing it for refunds when gas was expensive, effectively storing cheap gas to spend later. EIP-3529 slashed refunds to kill that game and to stop large refunds from destabilizing the new base-fee mechanism.
Because the gas bill is mostly storage, gas optimization is mostly storage discipline. The highest-leverage tactics, roughly in order of impact:
- Write storage as rarely as you can. Read a slot into a local memory variable, do all your work there, and write back once at the end — not inside a loop. One SSTORE beats ten.
- Pack variables into shared slots. A storage slot is 256 bits; four `uint64`s or a `uint64`+`address`+`bool` can share one slot, so the EVM updates them with a single SSTORE instead of several.
- Prefer calldata and memory for data that doesn't need to persist. Reading function arguments from calldata is far cheaper than copying them into storage.
- Emit events instead of storing data you only need off-chain. A LOG is far cheaper than an SSTORE, and indexers can read events — use storage only for state the contract itself must consult.
- Exploit warm/cold. If you must hit the same slot or external account twice, the second access is ~100 gas instead of ~2100 — so cache the first read and reuse it.