100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Storage, Memory, and Calldata

Solidity keeps data in three places — persistent storage, temporary memory, and read-only calldata — and choosing correctly shapes both gas cost and correctness.

Storage & Data StructuresIntermediate9 min readJul 10, 2026
Analogies

Where Solidity Keeps Your Data

Every variable in a Solidity contract lives in one of three data locations, and choosing the right one affects both correctness and gas cost. Storage is the contract's permanent state, written to the blockchain and surviving between transactions. Memory is a temporary workspace that exists only while a function runs. Calldata is a special read-only area holding the arguments passed to external functions. Value types default sensibly, but every reference type — arrays, structs, strings, bytes, and mappings — must declare its location explicitly.

🏏

Cricket analogy: A cricket setup keeps three spaces: the permanent ICC scorebook, the dressing-room whiteboard used for one session, and the umpire's read-only team sheet — matching storage, memory, and calldata exactly.

Storage: The Persistent Ledger

Storage is organized as a giant key-value array of 32-byte slots, one contract-wide namespace where all state variables live. It is by far the most expensive place to write: setting a previously-zero slot with SSTORE costs about 20,000 gas, and updating an existing slot about 5,000. Because of this, well-written contracts minimize and batch storage writes. Crucially, declaring a local variable as storage creates a pointer to existing state — assigning through it mutates the real contract data, not a copy.

🏏

Cricket analogy: Storage is the official scorebook — every run written costs effort and lasts forever, so like a scorer you update it sparingly rather than paying the 20,000-gas 'ink' price of a fresh slot.

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

contract DataLocations {
    uint[] public numbers; // storage state variable

    function process(uint[] calldata input) external {
        // 'input' lives in calldata — read-only and cheapest
        numbers = input; // copies calldata into storage

        uint[] memory temp = new uint[](input.length); // memory
        for (uint i = 0; i < input.length; i++) {
            temp[i] = input[i] * 2;
        }

        uint[] storage ref = numbers; // storage pointer (alias)
        ref.push(temp[0]); // mutates state directly
    }
}

Memory: The Temporary Scratchpad

Memory is a byte-addressable, function-scoped region that is zero-initialized at the start of each external call and discarded when the call returns. It is far cheaper than storage, which makes it the right place to build and manipulate reference types you don't need to persist — for example, constructing a temporary array with new uint[](n). Its cost is not free, though: the EVM charges for memory expansion, and that cost grows quadratically as you use more, so very large memory buffers can still become expensive.

🏏

Cricket analogy: Memory is the dressing-room whiteboard used to plan the batting order for one innings — cheap to scribble on and wiped after the match, unlike the permanent scorebook.

Rule of thumb for gas: use calldata for external parameters you only read, memory for data you need to build or mutate locally, and storage only for values that must persist between transactions. Every storage write is orders of magnitude more expensive than touching memory.

Calldata: The Read-Only Input

Calldata is the immutable, read-only region that holds the input data of a transaction or external call. Because it is read directly from the call payload and never copied, passing an external parameter as calldata is the cheapest option — and you cannot modify it, which prevents accidental mutation. For external functions whose reference-type arguments you only read, prefer calldata over memory; the compiler will even suggest it. When you need to change the data, copy it into memory first.

🏏

Cricket analogy: Calldata is the umpire's official team sheet handed in before play — you can read the lineup but cannot alter it mid-match, exactly like read-only calldata parameters.

Forgetting a data-location keyword on a reference type is a compile error, and accidentally taking a storage pointer when you meant a memory copy can silently mutate contract state. Always be explicit about which location you intend.

  • Storage is persistent contract state written to the blockchain; SSTORE to a fresh 32-byte slot costs about 20,000 gas.
  • Memory is a temporary, function-scoped region that is cleared when the function returns and is far cheaper than storage.
  • Calldata is a read-only, non-modifiable region holding the arguments of external calls — the cheapest location because nothing is copied.
  • Reference types (arrays, structs, strings, bytes, mappings) require an explicit data-location keyword in parameters and local variables.
  • Assigning a storage reference creates a pointer that mutates state directly, while assigning to a memory variable copies the data.
  • Mappings can only exist in storage; they cannot be declared in memory or calldata.
  • Prefer calldata over memory for external parameters you don't modify to save gas.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#SolidityStudyNotes#StorageMemoryAndCalldata#Storage#Memory#Calldata#Where#StudyNotes#SkillVeris#ExamPrep