BlockchainSmart ContractsSolidity17 min readUpdated

Struct Array in Solidity: A Beginner Smart Contract Guide

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

Cover illustration for: Struct Array in Solidity: A Beginner Smart Contract Guide

Section 01 · Definition

What is a struct array in Solidity?

A struct array is a list where each entry is a compound record. Every record packs several named fields together, and the array gives those records an ordered position.

Quick answer

What is a struct array? A struct array is an ordered list where every entry is a full struct instance. The type is written as YourStruct[]. Each entry occupies one or more storage slots depending on the struct fields. You add a record with push, look one up by index, and count items with length. You can also read or write any single field with students[i].fieldName.

The keyword struct lets you define a custom record type by listing its fields. Wrap that type in square brackets and you turn it into an array of records. So Student[] is a list of Student values, each one holding the same named fields in the same order.

Three indexed cells holding Student structs with name, score, and a pass or fail badge plus a fourth dashed cell labelled push.
A struct array stores compound records in numbered slots. Every cell holds the same set of named fields.

The mental model is a row of folders. Each folder sits at a numbered position and contains the same set of named fields on the inside. To pull the third folder you write students[2]. To peek at one field on that folder you write students[2].score. Indexes still start at zero, and the length still grows by one on every push.

Section 02 · Real use case

A simple real world example

A class teacher is recording students on chain. Each entry has a name, a score out of 100, and a passed flag derived from the score. The list grows every time a student submits.

The contract needs three operations. Append a new student. Read any student by position. Know how many students are on the list. A struct array does all three with the smallest possible code surface, and it keeps the three fields glued together so a single index always gives you the full record.

Other places this same shape shows up: a product catalogue where each row carries a name, a price, and a stock count; a voter roll where each entry carries an address, a weight, and a registration time; a todo list where each task carries a description, a deadline, and a done flag. Whenever a row needs more than one field, reach for a struct array.

Section 03 · Smart contract

A complete struct array smart contract

A small StudentRegistry contract with one struct, one storage array, and three functions. Short enough to read in two minutes, complete enough to deploy in Remix.

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

contract StudentRegistry {
    // 1. The struct definition. Every entry has these three fields.
    struct Student {
        string name;
        uint256 score;
        bool passed;
    }

    // 2. The storage array of student records.
    Student[] public students;

    // 3. Add a student. The passed flag is derived from the score.
    function addStudent(string memory name, uint256 score) external {
        students.push(Student({
            name: name,
            score: score,
            passed: score >= 50
        }));
    }

    // 4. Read one full student record by index.
    function getStudent(uint256 i) external view returns (Student memory) {
        require(i < students.length, "out of range");
        return students[i];
    }

    // 5. Return how many students are on the list.
    function totalStudents() external view returns (uint256) {
        return students.length;
    }
}

Paste this into Remix at remix.ethereum.org, compile with the 0.8.20 compiler, and deploy to the JavaScript VM. In the deploy panel call addStudent with values like ("Aisha", 85) and ("Bilal", 42). The public getter on students lets you read entries one at a time without writing any extra function.

Section 04 · Line by line

What every important line does

Walk through the contract one block at a time. The struct definition is the only new shape compared to the earlier posts in this beginner series.

solidity
struct Student {
    string name;
    uint256 score;
    bool passed;
}

The struct keyword introduces a custom record type. The fields are declared in order: a UTF-8 string, an unsigned integer, and a boolean. The compiler keeps the order, which matters when you construct a struct with positional arguments later.

solidity
Student[] public students;

The type Student[] declares a dynamic storage array where every slot holds a full Student record. The keyword public generates a getter that returns one record at a time. Solidity unpacks the fields for the auto generated getter, so a caller sees three return values rather than a single struct.

solidity
function addStudent(string memory name, uint256 score) external {
    students.push(Student({
        name: name,
        score: score,
        passed: score >= 50
    }));
}

The function constructs a new Student using named arguments and pushes it onto the array. Named arguments make the intent obvious and protect you from reordering the struct fields by accident. The passed flag is derived from the score so the caller only has to supply the inputs that are not derivable.

solidity
function getStudent(uint256 i) external view returns (Student memory) {
    require(i < students.length, "out of range");
    return students[i];
}

The return type is Student memory because the caller gets a fresh copy of the record, not a pointer into storage. The require guard rejects out of range indexes before the lookup runs. ABI encoding for the return takes care of packing the three fields into the response.

solidity
function totalStudents() external view returns (uint256) {
    return students.length;
}

The length is reported as a plain uint256 regardless of the element type. This pattern is the same across every Solidity array in this beginner series.

Section 05 · Reading and writing

Add data, get data, read one field

The three core operations match the earlier posts, with one struct specific perk: you can read or write a single field with dot notation, without copying the whole record.

Three labelled cards titled write, read, and length showing the matching Solidity code lines for adding a student, reading a record or a single field, and counting the array.
Reading, writing, and counting a struct array. The read line shows both shapes: the whole record and a single field.

Adding data uses students.push(Student(...)). You can use positional arguments like Student("Aisha", 85, true) or named arguments like Student({ name: "Aisha", score: 85, passed: true }). Named is safer because reordering struct fields later does not silently break old call sites.

Reading data uses bracket lookup. The whole record at index one is students[1]. A single field on that record is students[1].score. Field access works because Solidity treats the bracket result as a storage reference when the array is in storage, which lets you read or write the field directly.

Counting items uses students.length. The result is a uint256 you can compare in a require, use as an index bound for the methods in Section 06, or return for off chain code to read.

Section 06 · Methods

Add the array methods to StudentRegistry, one function at a time

The StudentRegistry contract from Section 03 only has push, indexed read, and length. Here are four more methods every beginner should know, added one function at a time so you can paste each into the same contract and try it in Remix as you go.

Start with the StudentRegistry contract you already have. Each function below goes right after the existing ones inside the contract body. None of them needs a for loop, and each one teaches a distinct rule of Solidity struct storage.

Function 1. Remove the last student with pop

solidity
function dropLast() external {
    require(students.length > 0, "empty");
    students.pop();
}

students.pop() drops the last record and shrinks the length by one. The require guard is critical: calling pop on a zero length array triggers a panic and the transaction reverts. Add the guard whenever the array can be empty at call time.

Function 2. Reset one slot with delete by index

solidity
function clearStudent(uint256 i) external {
    require(i < students.length, "out of range");
    delete students[i];
}

delete students[i] resets every field on that slot to the type default in one line. The name becomes the empty string, the score becomes zero, and the passed flag becomes false. The array length does not change. If zero score or empty name is legal in your domain, mark cleared slots some other way, for example by adding a removed flag to the struct.

Function 3. Wipe the entire array with delete on the variable

solidity
function clearAll() external {
    delete students;
}

delete students on the whole array resets every slot to its struct default and sets the length back to zero in one line. After the call, every indexed read goes out of range, and the next push starts a fresh list from index zero.

Function 4. Update a single field with direct assignment

solidity
function updateScore(uint256 i, uint256 newScore) external {
    require(i < students.length, "out of range");
    students[i].score = newScore;
    students[i].passed = newScore >= 50;
}

Bracket assignment plus dot notation lets you update one field at a time without copying the whole struct. The example writes a new score, then recomputes the derived passed flag in the same call so the record stays consistent. The length does not change because the slot already existed.

After adding the four functions, your StudentRegistry contract now covers all the basic array operations on a struct array: push, pop, delete by index, delete on the variable, and field level bracket assignment. Compile in Remix, redeploy, and try each new function from the deploy panel.

delete on a struct slot resets every field

A struct slot has no null state. delete writes the type default of every field at once: empty strings, zero integers, false booleans, and address(0) for any address. If any of those defaults is legal data in your domain, a cleared slot looks identical to a real one. Plan a removed flag on the struct, or remove the slot fully with swap and pop in the next section.

Section 07 · Removal

Function 5. Remove any student with swap and pop

When you need to drop a record from the middle of a struct array and you do not care about preserving the enrolment order, the swap and pop pattern removes it in constant time. Two writes and a pop, no iteration.

Move the last record into the slot you want to drop, then call pop. The targeted record disappears, the last record takes its place, and the length shrinks by one. Solidity copies all of the struct fields in a single storage assignment when you write students[i] = students[last].

solidity
function removeAt(uint256 i) external {
    uint256 last = students.length - 1;
    require(i <= last, "out of range");

    if (i != last) {
        students[i] = students[last];
    }
    students.pop();
}

The if (i != last) guard skips a tiny self assignment when the caller asks for the last slot. After the function returns, the array length is one less than before and the previous last student sits at the position that was removed. Names, scores, and passed flags all move together because they live inside the same struct.

Struct copies in storage cost more than uint copies

A struct assignment copies every field. A Student copy costs more gas than a uint copy because there is a string inside it, and string assignment copies the underlying bytes. For small structs the cost is still acceptable. For very wide structs, prefer the array plus mapping pattern that keeps records elsewhere and uses the array only for ordered ids.

Section 08 · More patterns

Return the whole array, build memory record lists, declare fixed length

Three more patterns added one at a time. The first goes onto StudentRegistry as Function 6. The other two are reference patterns you will reuse in other contracts.

Function 6. Return the whole array as one value

Returning the whole array works when the list is small. The return type uses Student[] memory because the caller receives a fresh copy of every record, not a pointer into storage. Add this function next to the rest in StudentRegistry.

solidity
function allStudents() external view returns (Student[] memory) {
    return students;
}

Pattern 1. Build a temporary memory array

Memory arrays let you build a temporary record list inside a function without writing to storage. The size is fixed at the moment you create them with new, so you cannot push or pop on a memory array. Set the entries by index after creation. This pattern lives on its own; it does not extend StudentRegistry because the function is pure.

solidity
function buildPreview() external pure returns (Student[] memory) {
    Student[] memory preview = new Student[](2);
    preview[0] = Student({ name: "Sample A", score: 90, passed: true });
    preview[1] = Student({ name: "Sample B", score: 30, passed: false });
    return preview;
}

Pattern 2. Declare a fixed length storage array

Fixed length storage struct arrays declare the slot count inside the brackets. You cannot push or pop because the size never changes. Every slot starts at every struct default and you write into the slots by index. This is a separate state variable on its own contract, not an extension of the dynamic StudentRegistry array.

solidity
// Three slot finalists array baked into storage.
Student[3] public finalists;

function setFinalist(uint256 i, string memory name, uint256 score) external {
    require(i < 3, "out of range");
    finalists[i] = Student({ name: name, score: score, passed: score >= 50 });
}

Pattern 3. storage reference for repeated field access

When a function touches several fields of the same record, a storage reference saves repeated storage reads. The reference is an alias into the array slot, so writes go straight back to storage. Use memory copies only when you want a snapshot that does not persist.

solidity
function bumpScore(uint256 i, uint256 delta) external {
    require(i < students.length, "out of range");
    Student storage s = students[i];
    s.score += delta;
    s.passed = s.score >= 50;
}

You cannot resize a dynamic storage array by writing to length

In Solidity 0.5 you could shrink an array with students.length = newLen. That assignment was removed in 0.6 and the modern compiler rejects it. Grow with push. Shrink with pop or delete. Reset fully with delete students. The length property is read only in every current Solidity version.

Section 09 · Final contract

The complete StudentRegistry with all six methods together

The original StudentRegistry from Section 03 plus the six methods you have added function by function. One paste ready file you can drop into Remix and try every operation on a struct array in a single session.

The contract below is the full version. The first three functions are the originals: push wrapped as addStudent, indexed read wrapped as getStudent, and the length read wrapped as totalStudents. The remaining six are the additions you walked through in Sections 06 to 08.

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

contract StudentRegistry {
    struct Student {
        string name;
        uint256 score;
        bool passed;
    }

    Student[] public students;

    // Append a new student.
    function addStudent(string memory name, uint256 score) external {
        students.push(Student({
            name: name,
            score: score,
            passed: score >= 50
        }));
    }

    // Read one full record by index.
    function getStudent(uint256 i) external view returns (Student memory) {
        require(i < students.length, "out of range");
        return students[i];
    }

    // Return how many students are on the list.
    function totalStudents() external view returns (uint256) {
        return students.length;
    }

    // Function 1. Remove the most recent student with pop.
    function dropLast() external {
        require(students.length > 0, "empty");
        students.pop();
    }

    // Function 2. Reset one slot with delete by index.
    function clearStudent(uint256 i) external {
        require(i < students.length, "out of range");
        delete students[i];
    }

    // Function 3. Wipe the whole list with delete on the variable.
    function clearAll() external {
        delete students;
    }

    // Function 4. Update one field with direct assignment.
    function updateScore(uint256 i, uint256 newScore) external {
        require(i < students.length, "out of range");
        students[i].score = newScore;
        students[i].passed = newScore >= 50;
    }

    // Function 5. Remove any student with swap and pop.
    function removeAt(uint256 i) external {
        uint256 last = students.length - 1;
        require(i <= last, "out of range");
        if (i != last) {
            students[i] = students[last];
        }
        students.pop();
    }

    // Function 6. Return the whole array as memory.
    function allStudents() external view returns (Student[] memory) {
        return students;
    }
}

Open Remix, paste the contract, compile with the 0.8.20 compiler, and redeploy to the JavaScript VM. The deploy panel now lists nine callable buttons plus the auto generated getter on students. Push a few records with addStudent, call allStudents to see them in order, run updateScore(0, 95) and confirm that passed also flipped, then call removeAt(0) and notice the enrolment order changes because swap and pop is unordered. Every method you have learnt in this post lives in this single file.

Section 10 · Beginner mistakes

Five mistakes new Solidity developers make with struct arrays

Using positional struct construction in fragile code

Student("Aisha", 85, true) works until someone reorders the struct fields. Then every call site silently writes the wrong field into the wrong slot. Prefer named arguments, like Student({ name, score, passed }), so the compiler tells you when fields move.

Treating delete as a remove

delete students[i] resets every field to the type default and leaves the length unchanged. The slot now looks identical to a brand new entry where every field happens to be zero. To actually remove a record, use pop, or use swap and pop when the index is not the last one.

Copying a storage struct into memory by accident

Writing Student memory s = students[i] copies the whole record. Modifying s does not change storage. If you wanted to mutate the slot, use Student storage s = students[i] so writes go straight back to storage.

Returning huge struct arrays from view functions

A view that returns the entire students array fails on production wallets when the list grows. Solidity has to copy every record and every string out of storage. Paginate the read with a start index and a count, or expose individual records through the public getter.

Forgetting to update derived fields

If passed is computed from score, every method that changes score must also rewrite passed. A direct write to one field leaves the other stale. Wrap the joint update in a helper, or recompute passed on read, so the two never diverge.

Section 11 · Practice task

Try this on your own

A small extension that locks in push, pop, delete, swap and pop, dot notation field updates, and storage references on a struct array. Try to ship it without copying from the contracts above.

Build a ProductCatalog contract

Define struct Product { string name; uint256 price; uint256 stock; }. Add a Product[] catalog state variable. addProduct(string memory name, uint256 price, uint256 stock) pushes a new record. getProduct(uint256 i) returns the record with a bounds check. productCount() returns the length. restock(uint256 i, uint256 delta) bumps the stock field through a storage reference. clearOut(uint256 i) zeroes the slot with delete by index. removeAt(uint256 i) compacts the list with swap and pop. allProducts() returns the array as Product[] memory. Bonus: emit a Restocked event whenever stock changes so an off chain dashboard can react.

Deploy the contract in Remix, push three products, then call restock(1, 50) and confirm the stock on index one jumps by fifty without touching the other fields. Try removeAt(0) next and notice the previous last product now sits at index zero. If the stock update fails, double check that you used a storage reference rather than a memory copy.

Section 13 · FAQ

Frequently asked questions

What is a struct array in Solidity in simple words?

A struct array is an ordered list where each entry is a full struct record. Every record packs several named fields together, and the array gives those records numbered positions. You add with push, read by index, count with length, and access a single field with dot notation like students[0].score.

How do I add a struct to a Solidity array?

Construct the struct with named or positional arguments and call push on the array. For example students.push(Student({ name: "Aisha", score: 85, passed: true })) appends a new student. Named arguments are safer because reordering struct fields later does not silently break existing call sites.

How do I read one field from a struct array slot?

Use bracket lookup followed by a dot and the field name. For example students[0].score reads the score on the first record without copying the whole struct. The same shape works for writes: students[0].score = 90 updates the field in place when the array is in storage.

What does delete do on a struct array slot?

delete on a single slot like delete students[2] resets every field in that record to the type default: empty strings, zero integers, false booleans, and address(0). The length does not change. delete on the whole array like delete students resets every slot and sets the length to zero in one line.

How do I remove a struct from the middle of a Solidity array without a loop?

Use the swap and pop pattern. Copy the last record into the slot you want to remove with students[i] = students[students.length - 1], then call students.pop(). Solidity copies every field of the struct in that single assignment. The enrolment order of the remaining records is no longer the order they joined the list.

How do I update a single field on a struct array record?

Either use direct dot assignment, like students[i].score = 90, or take a storage reference first when you touch several fields. Student storage s = students[i] gives you an alias into the slot, so writes through s go straight back to storage. Use memory copies only when you want a snapshot that does not persist.

Can I return the whole struct array from a Solidity function?

Yes. Declare the return type as YourStruct[] memory and write return students inside the body. The compiler copies every record out of storage into a fresh memory array, so the cost grows with both the number of entries and the size of each struct. Keep the call cheap by paginating with a start index and a count.

What is the difference between Student memory and Student storage?

Student memory holds a fresh copy of the record that lives only for the current call. Student storage holds an alias into a storage slot, so writes through the alias update the array on chain. Use storage when you want to mutate the record in place. Use memory when you want a snapshot you can read and modify without persisting.

Can I shrink a Solidity struct array by setting its length?

No. Solidity 0.5 allowed students.length = newLen, but the assignment was removed in 0.6 and the modern compiler rejects it. Grow the array with push, shrink it with pop or delete, and reset it fully with delete students. The length property is read only in every current Solidity version.

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 →