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.
// 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.
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 2The 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:
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.
// 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.
// 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
// 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.