Using Solidity immutable variables to reduce gas costs
Learn how immutable variables store constructor arguments in bytecode, lower gas reads, and avoid common pitfalls.
15 Sept 2026, 19:08 UTC

Why use immutable variables in Solidity
Immutable variables are declared with the immutable keyword, assigned exactly once in the constructor, and their values are embedded in the contract's creation bytecode. After deployment they behave like read‑only state variables but reading them costs less gas because the compiler replaces each read with a PUSH of the embedded value instead of a storage SLOAD.
Worked example
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Token {
string public immutable name;
string public immutable symbol;
uint8 public immutable decimals;
constructor(string memory _name, string memory _symbol, uint8 _decimals) {
name = _name;
symbol = _symbol;
decimals = _decimals;
}
function getInfo() external view returns (string memory, string memory, uint8) {
return (name, symbol, decimals);
}
}
When the contract is deployed, the values passed to the constructor become part of the creation bytecode. The getter functions name, symbol and decimals are automatically generated as public view functions; each returns the embedded constant via a PUSH instruction.
Verifying the bytecode substitution
- Compile the contract with
solc --asm Token.sol(requires a locally installed Solidity compiler, version ≥0.6.5). - In the generated assembly look for
PUSHopcodes immediately after the constructor code; they will contain the UTF‑8 encoded strings and the decimal value. - Alternatively, deploy the contract to a local testnet (e.g., Hardhat or Remix) and call
eth_getCodeon the deployed address; the runtime bytecode will show the samePUSHsequences.
No actual output is shown here; you can confirm the presence of the expected PUSH instructions by inspecting the assembly yourself.
Gas‑cost comparison
Calling the immutable getter name() typically costs around 3 gas (the exact number depends on the optimizer and EVM version) whereas reading a regular string state variable costs roughly 2100 gas for a SLOAD plus the cost of copying the data. The difference becomes noticeable when the getter is invoked frequently in loops or by other contracts.
Limits and common mistakes
- Size impact on deployment. Because the value is baked into the creation bytecode, each immutable adds its encoded size to the contract’s deployment transaction. Large strings, long arrays or big structs can make deployment prohibitively expensive.
- Not a replacement for
constant.constantvalues are resolved at compile time and cannot depend on constructor arguments. If you need a value that is known only after deployment (e.g., an address passed in), you must useimmutable. - Reassignment error. Attempting to assign to an immutable outside the constructor yields a compile‑time error such as
TypeError: Immutable variable ... cannot be assigned. The compiler prevents the mistake, but moving the assignment to a function (thinking it will work) is a common source of confusion. - Libraries.
immutablevariables cannot be declared inside a library; doing so triggersParserError: Expected ';' but got 'immutable'. Useinternalorpublicstate variables in the library if needed, or pass values as arguments. - CREATE2 does not change immutables. The salt used with CREATE2 only affects the contract address; the immutable values are still set by the constructor arguments and cannot be altered by the salt or initcode.
Practical checklist
- Declare the variable with
immutableand assign it once in the constructor. - Keep the value reasonably sized (prefer short strings, small numbers, or fixed‑size arrays).
- Verify the bytecode contains the expected
PUSHinstructions usingsolc --asmor a block explorer after deployment. - Use the built‑in gas profiler in Remix or Hardhat to compare getter costs against a regular state variable if you need quantitative proof.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.