View and Pure Functions in Solidity

Solidity gives you two function modifiers, view and pure, that let you declare upfront whether a function reads or modifies blockchain state. The compiler enforces these declarations, which means bugs get caught early and other developers can understand your code at a glance.

This post explains what each modifier does, what counts as reading or modifying state, and when to use each one.

What "State" Means in a Smart Contract

State is any data stored permanently on the blockchain. This includes:

  • Storage variables declared at the contract level
  • Account balances (including address(this).balance)
  • Data in other contracts
  • Block and transaction context (block.timestamp, msg.sender, etc.)

Reading or writing state costs gas. Functions that interact with state need to be part of a transaction, and transactions cost gas. Functions that do not touch state can sometimes be executed off-chain for free.

View Functions

A view function promises it will read state but never modify it. The compiler enforces this. If your function tries to write to a storage variable or emit an event, it will not compile as view.

contract Counter {
    uint256 public count = 0;

    // Reads state, returns it unchanged
    function getCount() public view returns (uint256) {
        return count;
    }

    // Reads state and does a calculation
    function getDoubleCount() public view returns (uint256) {
        return count * 2;
    }
}

When you call a view function directly from outside the blockchain (using eth_call), it costs no gas. The node executes it locally and returns the result. However, if a regular state-changing function calls a view function internally during a transaction, that internal call still consumes gas as part of the overall transaction cost.

Pure Functions

A pure function is more restricted than view. It cannot read state and cannot modify state. It only works with its input parameters.

contract MathHelper {
    // No state read, no state write
    function add(uint256 a, uint256 b) public pure returns (uint256) {
        return a + b;
    }

    function multiply(uint256 x, uint256 y) public pure returns (uint256) {
        return x * y;
    }
}

pure functions are useful for utility logic: hashing, encoding, arithmetic, or any computation that does not depend on contract data. Because they have no side effects and no dependencies on storage, they behave like mathematical functions. Same input always produces same output.

What Breaks Each Promise

These actions will cause the compiler to reject a view or pure declaration.

Breaks view (modifies state):

  • Writing to a storage variable
  • Emitting an event
  • Creating another contract
  • Using selfdestruct
  • Sending Ether
  • Calling a function that is not view or pure
  • Using low-level calls (call, delegatecall, etc.)
  • Writing state via inline assembly

Breaks pure (reads state):

  • Reading a storage variable
  • Accessing address(this).balance or any other address balance
  • Accessing block, tx, or msg properties (except msg.sig and msg.data)
  • Calling a function that is not pure
  • Reading state via inline assembly

msg.sig and msg.data are allowed inside pure functions. They describe the call itself, not the blockchain state.

All Three Types Together

Here is a single contract showing a state-modifying function, a view function, and a pure function side by side:

contract Example {
    uint256 public storedNumber = 42;

    // Regular function: modifies state
    function setNumber(uint256 newNumber) public {
        storedNumber = newNumber;
    }

    // View function: reads state, no modification
    function getNumber() public view returns (uint256) {
        return storedNumber;
    }

    // View function: reads state and transforms it
    function getNumberPlusTen() public view returns (uint256) {
        return storedNumber + 10;
    }

    // Pure function: no state involved
    function add(uint256 a, uint256 b) public pure returns (uint256) {
        return a + b;
    }
}

Why This Matters

Gas savings. External calls to view and pure functions execute off-chain for free when called directly. If your frontend or another service only needs to read data, declaring the function as view lets callers skip the transaction entirely.

Compiler enforcement. The compiler will reject a view function that tries to write state, and reject a pure function that tries to read it. This catches an entire class of accidental side effects before deployment.

Readability. A developer reading your code does not need to trace through the function body to understand whether it modifies anything. The keyword says it upfront.

When to Use Each

Use a regular function when you need to write to storage, emit events, or change contract state in any way.

Use view when you need to read storage variables, balances, or block context without changing anything.

Use pure when your function only needs its input parameters, nothing from storage or the environment.

If you are unsure which to use, start with pure. If the compiler rejects it because you are reading state, upgrade to view. If the compiler rejects that because you are writing state, remove the modifier entirely.