EVM Memory Explained: Layout, Expansion Gas, and Solidity Safety
Learn how EVM memory works, why expansion costs gas, how Solidity uses the free memory pointer, and which assembly mistakes can corrupt execution.

A Solidity function can look harmless yet consume surprising gas—or behave incorrectly in hand-written assembly—because one temporary workspace is easy to overlook. EVM memory is that workspace. Understanding it connects practical blockchain basics to the bytes a contract reads, changes, hashes, returns, and then discards.
Think of memory as a fresh roll of graph paper issued to each call frame. The contract can write notes anywhere on the roll, but unrolling farther costs gas. When that call ends, the paper is shredded. This guide explains the layout Solidity expects, the current expansion formula, and the safety boundaries that matter when you inspect or write low-level code.
What EVM memory is
During contract execution, the EVM has several data areas with different lifetimes. The Ethereum EVM documentation describes memory as a transient, byte-addressed workspace. It is separate from the 256-bit stack and from persistent contract storage.
Each message-call frame gets its own memory. A child call does not directly share the caller's memory; call opcodes copy designated input bytes into the child context and later copy return bytes back. Memory starts zeroed for a new frame and disappears with that frame, whether execution succeeds or reverts.
| Data area | Typical purpose | Writable? | Lifetime |
|---|---|---|---|
| Stack | Operands and small values | Yes | Current call frame |
| Memory | Mutable temporary bytes | Yes | Current call frame |
| Calldata | External call input | No | Current call frame |
| Storage | Contract state | Yes | Persists across transactions |
| Transient storage | Temporary contract state | Yes | Current transaction |
This distinction changes both semantics and cost. Copying a reference type from calldata or storage into memory creates an independent copy, while a memory-to-memory assignment can create another reference to the same object. Solidity documents those rules under data location and assignment behavior.
How Solidity lays out memory
The EVM itself exposes a flat byte array. Solidity adds conventions so compiled code and inline assembly agree about what each region means. Its official memory-layout documentation reserves the first 128 bytes:
| Byte range | Solidity convention |
|---|---|
0x00–0x3f | 64 bytes of scratch space for short-lived operations such as hashing |
0x40–0x5f | The word containing the free memory pointer |
0x60–0x7f | The zero slot, used as the initial pointer for empty dynamic arrays |
0x80… | The normal allocation area |
The value stored at 0x40 initially points to 0x80. High-level Solidity places new objects at the current free pointer and moves it forward; it does not provide a built-in way to free memory during the call.
Dynamic memory arrays begin with a 32-byte length followed by their elements. Most array elements occupy a multiple of 32 bytes even when their Solidity type is smaller, although bytes and string are packed exceptions. A fixed-size memory array has no length word. These rules differ from Ethereum contract storage slots, where small adjacent values can share a persistent 32-byte slot.
Warning
Do not assume unused space at the free memory pointer is zero. Solidity may use temporary memory beyond that pointer without advancing it, and its documentation explicitly warns that this area may not be zeroed.
MLOAD, MSTORE, and the highest byte rule
Three basic opcodes make the model concrete:
MLOAD(offset)reads 32 bytes beginning atoffset.MSTORE(offset, value)writes a full 32-byte word.MSTORE8(offset, value)writes the lowest byte ofvalue.
Memory grows when an operation accesses a non-empty range beyond the currently active size. The new size is rounded up to a 32-byte word boundary. It is the highest byte reached—not how many isolated bytes you intended to use—that determines expansion. A write at a very large offset can therefore demand all memory up to that point and run out of gas.
Memory is also consumed by operations that copy calldata or return data, hash a memory region, emit log data, call another contract, return successfully, or revert with data. Their opcode or per-word charges are separate from the incremental cost of expanding memory.
How EVM memory expansion gas is calculated
Under Ethereum's current EVM schedule, the total cost for an active memory size of a 32-byte words is:
Cmem(a) = 3 × a + floor(a² / 512)The Ethereum Yellow Paper defines this formula. An opcode pays only the difference between the cost after and before its access:
expansion cost = Cmem(words after) - Cmem(words before)That means expanding in several small steps does not avoid the eventual cumulative charge. The quadratic term is zero through 22 words and first becomes nonzero at 23 words. It then matters increasingly as the highest accessed offset grows.
| Active memory | Words | Total Cmem gas |
|---|---|---|
| 128 bytes | 4 | 12 |
| 704 bytes | 22 | 66 |
| 736 bytes | 23 | 70 |
| 1,024 bytes | 32 | 98 |
| 4,096 bytes | 128 | 416 |
These figures are raw EVM memory-expansion gas, not a user's transaction fee. The actual fee also depends on every other opcode, calldata, state access, the gas used, base fee, and priority fee. A complete gas-fee calculation must include that larger context.
The official execution-spec implementation independently expresses the same word rounding, linear term, quadratic term, and incremental charge. Fork rules can evolve, so gas-sensitive tooling should follow the target network's active specification rather than treat a blog formula as an eternal constant. For example, EIP-7686 proposes linear limits but is marked Stagnant; it is not the live rule described above.
A safe allocation pattern in inline assembly
Solidity's inline-assembly documentation gives the core allocation pattern:
function allocate(uint256 length) pure returns (uint256 pos) {
assembly ("memory-safe") {
pos := mload(0x40)
mstore(0x40, add(pos, length))
}
}Real allocation code normally rounds a byte length up when later objects require word alignment and checks that arithmetic cannot wrap. The important sequence is simple: read the pointer, reserve a non-overlapping region, then update the pointer before Solidity can allocate another object there.
The memory-safe annotation is not a runtime guard. It is a promise to the compiler that the block touches only permitted regions: Solidity-managed memory, properly allocated memory, the 0x00–0x3f scratch region, or qualifying temporary space beyond the pointer. The compiler may enable additional optimizer behavior based on that promise. A false annotation can cause incorrect, undefined behavior that tests may not expose.
Risks and common mistakes
Clobbering reserved slots
Leaving a changed value at 0x40 can make later allocations overlap. Writing to the zero slot at 0x60 can corrupt the default representation of empty dynamic arrays. Scratch space is temporary; values there must not be assumed to survive across high-level operations.
Confusing aliases with copies
Two memory variables can point to the same object. Mutating through one reference may change what the other sees. By contrast, moving data across memory, calldata, and storage boundaries can trigger a copy. Confirm the language's assignment rule instead of reasoning from variable names.
Expanding to an attacker-controlled offset
If untrusted input influences an assembly offset or length without bounds, one access can force extreme expansion and revert out of gas. Validate ranges before addition and before memory access. Test worst-case inputs, not just average ones.
Treating temporary as shared transaction state
Memory belongs to one call frame. It is not a channel for sharing a lock across nested calls or callbacks. When state must survive across call frames but only for the transaction, transient storage has different semantics and its own security considerations.
Practical review checklist
- Identify whether each reference value is in
memory,calldata, orstorage - Keep the
0x40free pointer valid and the0x60zero slot intact - Reserve non-overlapping regions before writing dynamic data
- Bound user-controlled offsets and lengths before arithmetic
- Include copy costs and expansion deltas in gas analysis
- Treat
memory-safeas a compiler promise, not a safety check - Test large arrays, revert data, return data, and nested calls
- Verify gas assumptions against the target fork and compiler version
FAQ
Is EVM memory persistent?
No. It belongs to a call frame and is discarded when that frame ends. Persistent contract values belong in storage; transaction-scoped, cross-call temporary state uses transient storage where appropriate.
Is memory always cheaper than storage?
They solve different problems. Memory avoids persistent state writes, but copying and expansion still cost gas. A large or sparse access can be expensive. Compare compiled bytecode and realistic inputs instead of applying a universal slogan.
Does reading memory expand it?
Yes, if a nonzero-length read reaches beyond the active size. Expansion is based on the highest byte the operation needs, rounded to 32-byte words.
Can Solidity memory be freed during a call?
High-level Solidity currently allocates forward and does not free objects. The whole frame's memory disappears when the call returns or reverts.
Primary sources
- Ethereum.org: Ethereum Virtual Machine
- Ethereum Yellow Paper: EVM and gas-cost specification
- Solidity: Layout in Memory
- Solidity: Reference Types and Data Locations
- Solidity: Inline Assembly and Memory Safety
- Ethereum Execution Specs: Memory gas calculation
- EIP-7686: Linear EVM memory limits (Stagnant proposal)
Treat memory as a bounded execution resource
EVM memory is temporary, but it is not free or structureless. Solidity's pointer conventions keep objects from colliding, while Ethereum's expansion schedule charges for the highest region a frame reaches. Read the data location, follow the free pointer, bound offsets and lengths, and measure the compiled path against the active fork.
This article is educational and not financial advice. Smart-contract bugs and crypto assets can cause complete loss. Verify current primary documentation, test before deploying or signing, use only amounts you can afford to lose, and do your own research (DYOR).
Keep learning

Ethereum Calldata Explained: How to Decode Transaction Input Data
Learn how Ethereum calldata encodes function selectors and arguments, how explorers decode it, and what to verify before signing a contract transaction.

Ethereum Contract Storage Slots: Packing, Mappings, and eth_getStorageAt
Learn how Solidity assigns Ethereum contract storage slots, how mappings and arrays derive locations, and how to inspect state safely with eth_getStorageAt.

Gas Fees Explained: A Complete Guide to Blockchain Transaction Costs
Learn how blockchain gas fees work, what drives costs up or down, practical tips to save on every transaction, and the risks every user should understand.
Explore related topics

Ethereum Glamsterdam Upgrade: ePBS, BALs, and What Is Actually Planned
Ethereum Glamsterdam is expected in Q4 2026. Learn its frozen scope, Sepolia milestone, ePBS, block-level access lists, and remaining uncertainties.
Crypto Address Poisoning: How to Verify Wallet Addresses Before You Send
Crypto address poisoning plants a lookalike address in your transaction history. Learn how it works and follow a safer verification checklist.