Using Solidity Immutable Variables to Cut Gas and Boost Security
Learn how declaring never‑changed state variables as immutable removes storage writes, lowers gas costs, and prevents accidental mutation, with a concrete ERC‑20 example and verification steps.
03 Feb 2026, 05:33 UTC

Problem: Unchanging state variables still cost storage and safety
Many contracts declare variables like name, symbol, or decimals that are set once at deployment and never modified. Even though their value is fixed, Solidity treats them as regular storage slots. This means:
- The constructor executes an
SSTOREto write the value, costing ~20 000 gas. - Every external read triggers an
SLOAD(~2 100 gas) even though the data never changes. - If a developer mistakenly assigns to the slot later, the contract’s behavior can be altered unintentionally.
These costs add up, especially for frequently called view functions, and the mutable slot introduces a surface for error.
Thesis: Immutable variables lock the value at deployment, eliminate storage writes, and cut gas
Starting with Solidity 0.6.5, the immutable modifier tells the compiler to:
- Allow a single assignment in the constructor (or at point of declaration).
- Place the value in the contract’s bytecode, not in storage.
- Replace reads with a cheap
PUSHof the constant, avoidingSLOAD.
The result is:
- No
SSTOREin the constructor. - No
SLOADon each read. - The value cannot be changed after deployment, preventing accidental mutation.
Worked example: ERC‑20 token with immutable metadata
Below is a minimal ERC‑20 where the token’s name, symbol, and decimals are declared immutable. The constructor receives these values from the deployer.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract SimpleToken is ERC20 {
string private immutable _name;
string private immutable _symbol;
uint8 private immutable _decimals;
constructor(string memory name_, string memory symbol_, uint8 decimals_, uint256 initialSupply) {
_name = name_;
_symbol = symbol_;
_decimals = decimals_;
_mint(msg.sender, initialSupply * 10 ** decimals_);
}
function name() public view returns (string memory) {
return _name;
}
function symbol() public view returns (string memory) {
return _symbol;
}
function decimals() public view returns (uint8) {
return _decimals;
}
}
What happens under the hood?
- The constructor stores only the
initialSupplymint operation; the three strings and theuint8are not written to storage. - When a caller invokes
name(), the EVM executes aPUSHof the constant string’s offset in the bytecode, costing roughly 3 gas versus the ~2 100 gas of anSLOAD. - Deployment gas is lower because three
SSTOREoperations are omitted.
Trade‑off: Immutability limits flexibility
Once a variable is marked immutable, its value is fixed for the lifetime of the contract. This poses challenges for:
- Upgradeable contracts: If you plan to use a proxy pattern and want to change metadata later, immutability prevents that.
- Future business logic: Should you ever need to adjust token decimals or rename the token, you must deploy a new contract.
If you anticipate needing to change the value after deployment, consider keeping the variable in storage and using an onlyOwner setter, or adopt a transparent proxy with a separate storage contract for mutable data.
Actionable guidance: Verify and adopt immutables safely
Follow these steps to ensure you gain the intended benefits without introducing bugs.
- Compiler version: Use Solidity ≥0.6.5. In Hardhat, set
"version": "0.8.20"insolcconfiguration. - Compile with optimization: Run
solc --optimize --bin SimpleToken.soland inspect the output. The immutable values appear in theruntimesection asPUSHinstructions, not as storage slots. - Deploy locally: Use Hardhat or Remix to deploy to a local network (e.g., Hardhat node). Record the deployment gas (
tx.gasUsed) and compare it to a version where the same variables are regularpublicstate variables. - Measure call gas: Call
name(),symbol(), anddecimals()repeatedly and note the gas consumed. The immutable version should show a steady ~3 gas per call, while the mutable version shows ~2 100 gas. - Static analysis: Run Slither (
slither . --detect immutable-variables) to confirm:- No assignments to the immutable after the constructor.
- The constructor assigns each immutable exactly once.
Limitations to keep in mind
- Immutables cannot be
mappingorstructtypes that require dynamic storage layout; they work best for value types and strings. - Because the value is baked into bytecode, the contract size grows linearly with the size of the immutable data. Very large immutables may hit the 24 KB contract size limit.
- If you need to change the value, you must redeploy; there is no upgrade path without a proxy.
By applying immutables to truly constant data, you reduce gas expenses for every interaction and eliminate a class of accidental‑mutation bugs. Verify with the steps above, weigh the flexibility trade‑off, and adopt the pattern where the data truly never changes after deployment.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.