storage vs memory vs calldata

You’ve probably seen the keywords storage, memory, and calldata but never quite understood what they do. Let’s fix it.

These keywords can be used in 3 places:

  1. annotating function arguments
  1. inside function logic
  1. annotating function return values


storage is easy:

  • a contract-level storage
  • stored in the blockchain
  • persistent through transactions

If being used in function arguments, can only be used for internal and private functions (‪functions callable only from within your contract)‬. Think about it, it does not make sense for an external or public caller to pass in a reference to your contract’s storage. Your contract’s storage is private!

If storage is being used in function logic, it can be used to get a reference to an element rather than copy it to memory:

storage can not be allocated at run time. It’s only allocated at contract creation time, and does not change ever. (It can still grow though if you use dynamic data structures such as an array or a mapping, but a new array/mapping can not be created in storage at run time).

It’s the most expensive type of storage because it persists data forever.

memory and calldata

The difference between memory and calldata is more subtle.

They are both used for temporary storage during function calls. Whenever a (external) function has finished execution, they are both cleared out.

But memory is persisted during internal and private function calls:

Both, memory and calldata, are cheaper than storage. But calldata is the cheapest.

calldata takes its name from “data from caller”. It’s a special place in memory used for function arguments. Thus, calldata can only be used in function arguments (not in function logic).

One good way to think about the difference between memory and calldata is that calldata is allocated by the caller. memory is allocated by your contract’s function. Thus memory is more expensive.

calldata is also read-only, but memory can be written to (even if memory is coming from the function argument).

calldata is the cheapest of all three but there are limits. When you use calldata for function args, your function becomes limited in the number of args that it can have.

You can allocate memory inside of function logic (calldata can not be allocated).

Annotating function return values

(only makes sense for internal and private functions)

When you use memory for function return values, the calling function can reuse the same allocated space without having to re-allocate.

When you use storage for return, the function will return a reference to storage.

The storage, memory, calldata keywords can only be used for complex types like arrays, mappings, and structs.

The primitive types like string, address, and uint will not take any modifiers - they are copied everywhere they are used.

Let me know your thoughts in this tweet👌

See also: