Skip to main content

Understanding `view` vs `pure` in Solidity: Semantics, Gas, and Best Practices

A practical guide to Solidity’s view and pure functions: what they actually guarantee, when they do and do not affect gas, and how to use them for clearer, safer smart contract design.

By GuillermoApril 10, 20266 min read

In Solidity, view and pure are not gas-optimization keywords. They are state-interaction guarantees.

  • A view function may read state, but must not modify it.
  • A pure function may neither read nor modify state.

These modifiers matter for correctness, composability, and audits because they tell both the compiler and human readers what a function is allowed to depend on.

What each modifier allows

A view function can read:

  • contract storage
  • external contract state through read-only calls
  • block and transaction context such as block.timestamp, block.number, or msg.sender

A pure function can depend only on its inputs and local variables. It cannot read storage, external state, or block/transaction context.

At the EVM level, read-only external calls are enforced with STATICCALL. That matters because view is partly a Solidity-level promise, while STATICCALL is an execution-level restriction.

Code Examples

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract ViewVsPure {
    uint256 public totalSupply = 1_000_000e18;
    mapping(address => uint256) public balances;

    // ── PURE: only uses input parameters and memory ──

    /// @notice Basis-point tax calculation (10000 = 100%)
    function calculateTax(uint256 amount, uint256 bps) external pure returns (uint256) {
        return (amount * bps) / 10_000;
    }

    /// @notice Deterministic hash — same input always yields same output
    function hashData(bytes memory data) external pure returns (bytes32) {
        return keccak256(data);
    }

    // ── VIEW: reads storage or block context ──

    /// @notice Single storage read
    function getBalance(address user) external view returns (uint256) {
        return balances[user];
    }

    /// @notice Reads block context — not allowed in pure
    function getCurrentTimestamp() external view returns (uint256) {
        return block.timestamp;
    }

    /// @notice Reads balances[user] and totalSupply
    function calculateUserShare(address user) external view returns (uint256) {
        uint256 supply = totalSupply;
        if (supply == 0) return 0;
        return (balances[user] * 1e18) / supply; // 18-decimal precision
    }

    // ── STATE-CHANGING: costs gas in every transaction context ──

    function updateBalance(address user, uint256 amount) external {
        balances[user] = amount;
    }
}

Gas: When Read-Only Is Free — and When It Is Not

view and pure functions do not cost onchain gas when invoked off-chain with eth_call, because no transaction is broadcast or included in a block.

But when the same logic executes inside a transaction, it consumes gas like any other code path.

That means:

  • calling a view function from your frontend is usually “free” in the onchain sense
  • calling a view function from inside a state-changing function is not free
  • marking a function pure instead of view does not magically lower gas; it only prevents state reads, which may indirectly avoid expensive operations like SLOAD

A useful mental model is:

view and pure describe what a function is allowed to read, not how the EVM prices its execution.

Here is the common source of confusion:

contract GasExample {
    uint256 public price = 1 ether;

    function getPrice() public view returns (uint256) {
        return price;
    }

    function buy(uint256 qty) external payable {
        // This view call happens inside a transaction.
        // Its storage read still costs gas.
        uint256 cost = getPrice() * qty;
        require(msg.value >= cost, "Insufficient payment");
    }
}

An internal call to a view or pure function is just normal bytecode execution. It is not a special free operation.

Opcode Cost Reference (Post-EIP-2929)

OpcodeColdWarmTypical meaning
SLOAD2100100storage read
BALANCE2600100account balance read
EXTCODESIZE2600100code existence / size check
STATICCALL2600 + exec100 + execread-only external call
stack / memory ops~3–6~3–6typical pure-function work

Real-World Application Patterns

Use pure for:

  • mathematical libraries
  • encoding and decoding helpers
  • signature and hash construction
  • validation predicates that depend only on inputs

Use view for:

  • aggregated state queries
  • computed properties derived from storage
  • access-controlled reads involving msg.sender or role state
  • oracle or adapter reads from external contracts

Common Pitfalls

// ❌ Compiler error: block context is not allowed in pure
function badPure() public pure returns (uint256) {
    return block.timestamp;
}

// ❌ Compiler error: state writes are not allowed in view
function badView() public view returns (uint256) {
    totalSupply = 1000;
    return totalSupply;
}

// ✅ Compiles, but the modifier is weaker than necessary
function add(uint256 a, uint256 b) public pure returns (uint256) {
    return a + b;
}

// ❌ Not pure: depends on external contract state
function externalSupply(IERC20 token) public view returns (uint256) {
    return token.totalSupply();
}

When a view function calls another contract, the compiler requires view, not pure, because the result depends on external state. That value may change across blocks, so frontend caching assumptions should reflect that.

Security Considerations

pure functions reduce chain-state-related risk because they have no storage reads, no external state dependency, and no block-context dependency. That makes them easier to isolate, test, and audit. But they are not automatically “safe” — math bugs, encoding errors, and flawed assumptions can still exist inside pure logic.

view functions carry more nuance:

  • external read-only calls use STATICCALL, which the EVM enforces as non-state-modifying
  • private only restricts Solidity-level access, not what the declaring contract itself can read internally
  • off-chain users can inspect storage slots directly regardless of visibility modifiers
  • external view calls are only as trustworthy as the contracts they read from

Never treat onchain storage as secret just because a variable is marked private.

Actionable Takeaways

  1. Use pure when logic depends only on inputs and local computation.
  2. Use view when logic needs storage, external read-only state, or block/transaction context.
  3. Treat view and pure as correctness annotations first, not gas tricks.
  4. Remember that eth_call avoids onchain gas, but the same logic still costs gas when executed during a transaction.
  5. Split complex flows into pure computation plus state-reading wrappers where possible.
  6. Be cautious with external view calls: read-only does not mean trustworthy.