BlockchainSolidity9 min readUpdated

Arrays in Solidity: Fixed, Dynamic, and Safe Iteration

By Mudassir Khan — Agentic AI Consultant & AI Systems Architect, Islamabad, Pakistan

Cover illustration for: Arrays in Solidity: Fixed, Dynamic, and Safe Iteration

Section 01 · Definition

What is an array in Solidity?

An array is an ordered list of values of the same type. Solidity supports fixed-length arrays where the size is part of the type, and dynamic arrays where the size grows at runtime.

Quick answer

What is an array? A Solidity array is a contiguous list of values of one type, accessed by zero-based index. Fixed-length arrays declare their size as part of the type — uint256[3] holds exactly three uints. Dynamic arrays declare an empty pair of brackets — uint256[] starts empty and can grow with .push(). Both kinds support indexing and reading .length.

solidity
// Fixed-length array — size baked into the type
uint256[3] public scores = [uint256(10), 20, 30];

// Dynamic array — grows at runtime
address[] public players;

// Dynamic array of structs
struct Bid { address bidder; uint256 amount; }
Bid[] public bids;

Section 02 · Operations

The handful of operations Solidity actually gives you

Arrays support fewer operations than you would expect from JavaScript or Python. Knowing what is and is not available avoids hours of compiler errors.

Four card grid showing array operations: push, pop, length, delete with notes.
The full Solidity array API. No splice, no slice, no map, no filter, no sort.
solidity
uint256[] public list;

list.push(7);                  // append → list = [7]
list.push(11);                 // append → list = [7, 11]
list.push(13);                 // append → list = [7, 11, 13]

uint256 first = list[0];       // 7
uint256 n     = list.length;   // 3

list.pop();                    // remove last → list = [7, 11]

delete list[0];                // resets to 0 → list = [0, 11]
                               // length is STILL 2

The big surprise is delete: it zeroes out the slot at the given index but does not shift later elements down or reduce the length. To remove an element by index without leaving a hole, use the swap-and-pop pattern:

solidity
function removeAt(uint256 i) external {
    require(i < list.length, "out of bounds");
    list[i] = list[list.length - 1];   // overwrite with last
    list.pop();                        // shrink by one
}

Section 03 · Storage vs memory vs calldata

Where the array lives changes what you can do with it

A storage array can grow with .push and persists across transactions. A memory array has fixed length set at allocation. A calldata array is read-only and the cheapest of the three.

solidity
// Storage array — declared at contract level, persists
address[] public allHolders;

// Memory array — fixed size, cleared at end of call
function topThree() external pure returns (uint256[] memory) {
    uint256[] memory out = new uint256[](3);
    out[0] = 1; out[1] = 2; out[2] = 3;
    return out;
}

// Calldata array — read-only input, cheapest
function airdrop(address[] calldata recipients, uint256 amount) external {
    for (uint256 i = 0; i < recipients.length; i++) {
        token.transfer(recipients[i], amount);
    }
}

The rule of thumb: declare external function inputs as calldata whenever you do not need to modify them. Use memory for temporary working arrays inside a function. Reach for storage when you genuinely need to persist between transactions.

Section 04 · Iteration safety

Why unbounded loops are dangerous

A loop over an array whose length depends on user input can be made to run out of gas. That is the classic denial-of-service bug in Solidity.

solidity
// DANGEROUS — anyone can call addToList; later refund() loops over everyone
function addToList() external { participants.push(msg.sender); }

function refundAll() external onlyOwner {
    for (uint256 i = 0; i < participants.length; i++) {
        payable(participants[i]).transfer(refundAmount);
    }
    // attacker calls addToList() 10 000 times → refundAll runs out of gas → contract bricked
}

// SAFER — paginate
function refundBatch(uint256 start, uint256 count) external onlyOwner {
    uint256 end = start + count;
    if (end > participants.length) end = participants.length;
    for (uint256 i = start; i < end; i++) {
        payable(participants[i]).transfer(refundAmount);
    }
}

The pull-payment alternative

Even better than batching: store balances in a mapping(address => uint256) and let each user claim their own funds with a withdraw() function. The contract never iterates the list. This is OpenZeppelin's PullPayment pattern and is the standard for distributing rewards in DeFi.

Section 05 · Real-world uses

Where arrays show up in production code

solidity
// 1. Multisig signers — small fixed committee
address[] public signers;

// 2. Governance proposals — append-only history
struct Proposal { string title; uint256 yes; uint256 no; uint64 endsAt; }
Proposal[] public proposals;

// 3. Merkle proof input
function claim(bytes32[] calldata proof, uint256 amount) external {
    // ...
}

// 4. Snapshot of token holders for a one-time airdrop
address[] public snapshotHolders;

Section 07 · FAQ

Frequently asked questions

What is the difference between fixed and dynamic arrays?

A fixed array (uint256[10]) has its length baked into the type — you can never add or remove elements, only overwrite the slots. A dynamic array (uint256[]) starts at length zero and grows with .push(). Fixed arrays save a bit of gas because the compiler knows the layout at compile time.

How do I remove an element from the middle of an array?

Solidity has no built-in splice. The standard trick is swap-and-pop: copy the last element into the slot you want to remove, then call .pop(). This costs O(1) but does not preserve order. If you need to preserve order, you have to shift every later element down by one, which is O(n) and gas-intensive.

Can I declare an empty fixed-size array?

No — the size is part of the type. uint256[0] is not allowed. If you do not know the size at compile time, use a dynamic array.

Why does delete a[i] not shrink the array?

Because shrinking would invalidate every index after i and silently change the meaning of code that holds those indices. Solidity prefers to leave the slot in a known default state (zero) and let the developer choose whether to shift or pop.

When should I use a mapping instead of an array?

Use an array when the order matters or you need to enumerate every entry on chain. Use a mapping when you only ever look up by a known key and never need to iterate. Most contracts use both — a mapping for fast lookup, plus a parallel array of keys when enumeration is occasionally needed.

Written by Mudassir Khan

Agentic AI consultant and AI systems architect based in Islamabad, Pakistan. CEO of Cube A Cloud. 38+ agentic AI launches delivered for global founders and CTOs.

View agentic AI consulting serviceSee ChainTrust case study

Related service

Agentic AI Consulting

See scope & pricing →

Related case study

ChainTrust Compliance Engine

Read case study →

More on this topic

Need an AI systems architect?

Book a 30-minute architecture call. I will sketch the high-level design for your use case and give you an honest view of the trade-offs.

Book a strategy call →