Solidity's immutable Keyword: Gas Savings and Design Trade-offs
Solidity's immutable keyword eliminates constructor storage writes and turns reads into cheap immediate loads. This post shows a fee-collector example, measures ~45k gas saved on deployment, and outlines when immutability is the wrong choice.
26 Jan 2026, 11:36 UTC

The Problem: Storage Writes Cost Gas
Every SSTORE opcode in a contract constructor burns gas. For state variables that never change after deployment—admin addresses, fee denominators, chain IDs—paying for a write on every deployment is wasteful. Solidity 0.6.5 introduced immutable to eliminate that write entirely, replacing it with a literal embedded in the deployed bytecode.
How immutable Works
An immutable variable can be assigned once, either at declaration or inside the constructor. After construction finishes, the compiler treats the variable as a constant: reads compile to a PUSH of the literal value instead of an SLOAD. No storage slot is written, so deployment gas drops and every subsequent read is cheaper.
Worked Example: Fee Configuration
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract FeeCollector {
address immutable feeRecipient;
uint256 immutable feeBps; // basis points, e.g., 250 = 2.5%
constructor(address _recipient, uint256 _bps) {
feeRecipient = _recipient;
feeBps = _bps;
}
function collectFee(uint256 amount) external returns (uint256 fee) {
fee = (amount * feeBps) / 10_000;
payable(feeRecipient).transfer(fee);
}
}
Compare this with a version using regular public state variables. In the regular version the constructor emits two SSTORE operations; the immutable version emits none for those variables. On a recent Hardhat local fork (London rules, 0.8.20), deploying the immutable variant cost roughly 45,000 gas less than the mutable equivalent. Each call to collectFee also saves an SLOAD (≈2,100 gas) because the values are pushed as immediates.
Verification Steps
- Inspect bytecode: Run
solc --bin FeeCollector.sol | grep -c SSTORE(or useforge inspect FeeCollector bytecode). The immutable contract should show zeroSSTOREfor the two variables. - Measure deployment gas: Deploy both variants to a local testnet (Hardhat, Foundry, or Ganache) and record
tx.gasUsed. The difference should match the avoided storage writes (≈20,000 gas perSSTOREon a cold slot). - Check assembly: In Remix, open the "Compilation Details" → "Assembly" view. The constructor will show
PUSH20/PUSH2for the immutable values instead ofSSTORE.
Trade-offs and Limitations
- No post-deployment changes. If the fee recipient must rotate or the basis-point rate needs governance updates,
immutableis the wrong tool. Use a regular state variable behind anonlyOwnersetter or a proxy pattern. - Storage slot still reserved. The compiler allocates a slot for each immutable variable to keep storage layout stable across upgrades. It does not shrink the contract's storage footprint—only the write cost.
- Constructor complexity. Values that depend on external calls (e.g., reading another contract's state) cannot be used to initialize immutables because the constructor cannot make external calls before the contract exists.
When to Choose immutable
Use immutable for values that are truly constant for the life of the contract: chain IDs, fixed protocol parameters, factory-deployed clone addresses, or admin addresses in non-upgradable contracts. For anything that might need governance, migration, or emergency intervention, stick with mutable storage and accept the gas cost—or design an upgradeable proxy where the implementation contract can still use immutables for its own fixed parameters.
Quick Checklist
- Value never changes after deployment? →
immutable - Value set once in constructor from constructor arguments or constants? →
immutable - Value might be updated by admin, governance, or migration? → regular state variable + access control
- Need to read the value frequently in hot paths? →
immutablesaves ~2,100 gas per read
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.