Yesterday we took our first real steps into Solidity: variables, functions, and a simple Web3JourneyLogger contract that stored a single note on-chain. Today we’re going to upgrade that tiny contract into something closer to how real dApps manage data.
This is Day 27 of the 60‑Day Web3 journey, still in Phase 3: Development. The goal for today is to understand how Solidity handles collections of data : arrays, mappings, and structs and then use them together to build a multi‑entry, multi‑user on-chain journal.
1. Why we need more than simple variables
Storing a single dayNumber and note is cute, but it doesn’t scale.
Real smart contracts usually need to:
- Track many pieces of data, not just one (multiple entries, multiple users).
- Group related data into records (like “user profile”, “order”, “position”).
- Look up data quickly by a key like an address or an ID.
In normal programming, you’d reach for arrays, dictionaries, and objects. In Solidity, you get very similar tools:
- Arrays → ordered lists of items.
- Mappings → key → value lookups (like hash maps).
- Structs → custom data types that bundle fields together.
We’ll see each one separately, then combine them into a better Web3 Journey Logger.
2. Arrays: storing ordered lists
An array in Solidity is an ordered list of elements of the same type.
2.1 Fixed vs dynamic arrays
There are two main kinds of arrays:
- Fixed-size array
uint256 public fixedNumbers;
This always has exactly 5 elements. You cannot grow or shrink it.
- Dynamic array
uint256[] public numbers;
This can grow as you push new elements to it.
For most dApp use cases, you’ll use dynamic arrays.
2.2 Basic operations on dynamic arrays
Here’s a tiny example:
uint256[] public daysLearned;
function addDay(uint256 _day) public {
daysLearned.push(_day); // add element at the end
}
function getDayAtIndex(uint256 index) public view returns (uint256) {
return daysLearned[index];
}
function getTotalDays() public view returns (uint256) {
return daysLearned.length;
}
Key points:
-
pushappends a new element. - You access elements with
array[index]. - You can check
array.lengthto know how many items are stored.
Arrays are great for ordered lists, but they aren’t efficient for lookups like “give me the entry for this address”. For that, we use mappings.
3. Mappings: key–value storage on-chain
A mapping is like a dictionary or hash map: you give it a key, and it gives you a value.
3.1 Basic mapping syntax
The general form:
mapping(KeyType => ValueType) public myMapping;
Example:
mapping(address => uint256) public entryCountByUser;
This mapping says: “for each address, store a uint256 count”.
If you do:
entryCountByUser[msg.sender] = 5;
then entryCountByUser[msg.sender] will later return 5.
3.2 Important mapping quirks
Mappings have some important properties:
- They behave like infinite default dictionaries.
- Any key you haven’t set yet returns the default value for that type (
0,false, address0x0, etc.).
- Any key you haven’t set yet returns the default value for that type (
-
They are not iterable.
- You can’t “loop over all keys” from inside the contract.
- If you need iteration, you must keep a separate array of keys or a counter.
Because of these quirks, mappings are best used for “given a key, fetch the value” patterns, like:
- address → user profile
- token ID → owner
- order ID → order struct
4. Structs: custom data types
A struct lets you define your own data shape by grouping fields together.
4.1 Defining and using a struct
Example struct:
struct Entry {
uint256 day;
string note;
}
You can use it like this:
Entry public latestEntry;
function setLatestEntry(uint256 _day, string calldata _note) public {
latestEntry = Entry({day: _day, note: _note});
}
Structs are especially powerful when combined with arrays and mappings.
5. Upgrading the Web3 Journey Logger
Let’s upgrade yesterday’s contract to support multiple entries per user and multiple users.
5.1 New design
We want:
- A struct
Entryrepresenting(day, note). - For each user address, an array of
Entrystructs. - Helper functions to:
- Add a new entry.
- Read a single entry by index.
- Get how many entries a user has.
This leads to a pattern like:
mapping(address => Entry[]) public entriesByUser;
Which you can read as: “for each address, store an array of Entry structs”.
5.2 Full upgraded contract
Here’s a full version of an upgraded Web3JourneyLoggerV2:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Web3JourneyLoggerV2 {
// --- Structs ---
struct Entry {
uint256 day;
string note;
}
// --- State variables ---
// Address of the person who deployed the contract
address public owner;
// Name or handle of the learner (optional global name)
string public name;
// For each user address, store an array of their entries
mapping(address => Entry[]) private entriesByUser;
// --- Events ---
event EntryAdded(address indexed user, uint256 indexed day, uint256 index, string note);
// --- Constructor ---
constructor(string memory _name) {
owner = msg.sender;
name = _name;
}
// --- Core functions ---
/// @notice Add a new journal entry for the caller
function addEntry(uint256 _day, string calldata _note) public {
Entry memory newEntry = Entry({day: _day, note: _note});
entriesByUser[msg.sender].push(newEntry);
uint256 index = entriesByUser[msg.sender].length - 1;
emit EntryAdded(msg.sender, _day, index, _note);
}
/// @notice Get a specific entry for a user by index
function getEntry(address _user, uint256 _index)
public
view
returns (uint256 day, string memory note)
{
require(_index < entriesByUser[_user].length, "Index out of bounds");
Entry storage entry = entriesByUser[_user][_index];
return (entry.day, entry.note);
}
/// @notice Get how many entries a user has
function getEntryCount(address _user) public view returns (uint256) {
return entriesByUser[_user].length;
}
}
A few things to notice:
-
Entryis a struct containingdayandnote. -
mapping(address => Entry[]) private entriesByUser;creates a mapping from user to an array of their entries. -
addEntrybuilds a newEntryin memory, pushes it into the caller’s array, and emits anEntryAddedevent. -
getEntrylets you read a specific entry by user and index. -
getEntryCounttells you how many entries a user has so front-ends can loop over them off-chain.
This contract is no longer just a single note; it’s a tiny, multi‑user journaling dApp backend.
6. Deploying V2 and testing it
You can deploy Web3JourneyLoggerV2 using the same Remix + Sepolia flow from yesterday:
- Open Remix and create
Web3JourneyLoggerV2.sol. - Paste the full contract above.
- Compile with a
0.8.xcompiler. - In “Deploy & Run Transactions”, choose Injected Provider – MetaMask and the Sepolia network.
- Deploy with a name (e.g.,
"Web3ForHumans").
Once deployed:
- Call
addEntry(27, "Learned arrays, mappings, and structs today")from your wallet. - Call
getEntryCount(yourAddress)— you should see1. - Call
getEntry(yourAddress, 0)to read back the first entry.
Ask a friend to connect their wallet and call addEntry too. Now your contract is tracking multiple learners and their progress on-chain.
7. Why arrays + mappings + structs matter
These three tools — arrays, mappings, and structs — are the backbone of almost every serious Solidity contract:
- A DEX might use
mapping(address => mapping(address => uint256))to track token balances. - A DAO might use structs + arrays for proposals and votes.
- An NFT contract uses mappings from token IDs to owners and metadata.
By understanding how to combine them, you’re no longer just “deploying example contracts” — you’re modeling real-world data structures on-chain.
Tomorrow, we can build on this by adding more features like:
- Editing or deleting entries.
- Restricting certain actions to the contract owner.
- Or exposing your journal data to a simple frontend.
For now, if you deploy Web3JourneyLoggerV2 on Sepolia, share your contract address — let’s see how many on-chain learning journals we can spin up.
Further reading
-
Solidity official docs – Arrays, Structs, and Mappings
-
Structs, Mappings and Arrays in Solidity
-
Understanding mappings in Solidity
- Follow the series on Medium | Twitter | Future
- Jump into Web3ForHumans on Telegram and we’ll brainstorm Web3 together.