Solidity Data Types, Mappings and Structs
In this tutorial, you'll learn Solidity data types including value types, mappings, structs, arrays, and storage location best practices. Why it matters: choosing the right data structures in Solidity directly impacts gas costs, contract security, and overall efficiency of your decentralized applications. By the end, you'll confidently use mappings, structs, and arrays in your contracts.
Solidity data types fall into two categories: value types (passed by value) and reference types (passed by reference), with storage locations determining how data persists and how much gas operations cost.
Value Types in Solidity
Value types include booleans, integers, addresses, and fixed-size byte arrays. They are copied when assigned or passed to functions.
| Type | Size | Range | Example |
|---|---|---|---|
| bool | 1 byte | true/false | bool public isActive = true; |
| uint256 | 32 bytes | 0 to 2^256-1 | uint256 public balance = 1000; |
| uint8 | 1 byte | 0 to 255 | uint8 public small = 200; |
| int256 | 32 bytes | -2^255 to 2^255-1 | int256 public temp = -10; |
| address | 20 bytes | Ethereum address | address public owner = msg.sender; |
| address payable | 20 bytes | Address with transfer | payable(addr).transfer(1 ether); |
| bytes32 | 32 bytes | Fixed bytes | bytes32 public hash; |
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract ValueTypesDemo {
bool public isComplete;
uint256 public counter;
address public creator;
constructor() {
isComplete = false;
counter = 100;
creator = msg.sender;
}
function toggle() external {
isComplete = !isComplete;
}
function increment(uint256 _amount) external {
// SafeMath is built-in in Solidity 0.8+
counter += _amount;
}
function getCreatorBalance() external view returns (uint256) {
return creator.balance;
}
}
Expected behavior: toggle() flips isComplete between true and false. increment(5) changes counter to 105. getCreatorBalance() returns the creator's ETH balance.
Understanding Mappings
Mappings are key-value stores that provide O(1) lookup. They are the most commonly used data structure in Solidity for tracking balances, permissions, and state.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract MappingExample {
// Simple mapping: address => balance
mapping(address => uint256) public balances;
// Nested mapping: tokenId => owner => allowance
mapping(uint256 => mapping(address => uint256)) public allowances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 _amount) external {
require(balances[msg.sender] >= _amount, "Insufficient balance");
balances[msg.sender] -= _amount;
payable(msg.sender).transfer(_amount);
}
function setAllowance(uint256 _tokenId, address _spender, uint256 _amount) external {
allowances[_tokenId][_spender] = _amount;
}
}
Expected behavior: Users deposit ETH and their balance increases. They withdraw up to their balance. Token owners can set allowances for spenders on specific token IDs.
Working with Structs
Structs let you define custom data types combining multiple fields. They are often used with mappings to create structured records.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract StructExample {
struct User {
string name;
uint256 age;
address wallet;
bool isVerified;
}
mapping(address => User) public users;
address[] public userList;
function registerUser(string memory _name, uint256 _age) external {
require(bytes(users[msg.sender].name).length == 0, "Already registered");
User storage newUser = users[msg.sender];
newUser.name = _name;
newUser.age = _age;
newUser.wallet = msg.sender;
newUser.isVerified = false;
userList.push(msg.sender);
}
function verifyUser(address _user) external {
users[_user].isVerified = true;
}
function getUserCount() external view returns (uint256) {
return userList.length;
}
}
Expected behavior: Users register with name and age. An admin verifies them. getUserCount() returns total registered users. Each user struct is stored in a mapping keyed by address.
flowchart LR A[User Address] --> B[mapping
address => User] B --> C[User Struct] C --> D[name] C --> E[age] C --> F[wallet] C --> G[isVerified]
Arrays in Solidity
Solidity supports dynamic and fixed-size arrays. Storage arrays are expensive to modify but efficient to read.
| Array Type | Syntax | Gas Cost | Use Case |
|---|---|---|---|
| Fixed | uint256[10] |
Low | Known size, limited elements |
| Dynamic | uint256[] |
High | Unknown or growing size |
| Storage | storage ref |
Very high | Persistent array |
| Memory | memory ref |
Low | Temporary processing |
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract ArrayExample {
uint256[] public dynamicArray;
uint256[10] public fixedArray;
function addElement(uint256 _value) external {
dynamicArray.push(_value);
}
function removeLast() external {
require(dynamicArray.length > 0, "Empty array");
dynamicArray.pop();
}
function getArray() external view returns (uint256[] memory) {
return dynamicArray;
}
function sumArray() external view returns (uint256) {
uint256 total;
uint256 len = dynamicArray.length;
for (uint256 i; i < len; i++) {
total += dynamicArray[i];
}
return total;
}
}
Expected behavior: addElement(42) appends to the array. removeLast() removes the last element. getArray() returns all elements. sumArray() returns the total.
Data Location Rules
Understanding storage, memory, and calldata is critical for writing efficient Solidity.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract DataLocationDemo {
struct Item {
uint256 id;
string name;
}
mapping(uint256 => Item) public items;
// calldata: read-only, cheapest for external params
function addItem(uint256 _id, string calldata _name) external {
items[_id] = Item(_id, _name); // Copy from calldata to storage
}
// memory: temporary copy, good for processing
function getItem(uint256 _id) external view returns (Item memory) {
return items[_id]; // Copy from storage to memory
}
// storage: direct reference, expensive first access
function updateItem(uint256 _id, string calldata _name) external {
Item storage item = items[_id]; // Reference to storage
item.name = _name; // Modify directly in storage
}
}
Expected behavior: addItem stores data from calldata to storage. getItem copies from storage to memory. updateItem directly modifies storage via a reference.
Common Errors and Misunderstandings
1. Unbounded Array Loops
Iterating over dynamic arrays that grow unbounded can exceed block gas limit. Use mappings with separate length tracking or pagination.
2. Mapping Iteration
Mappings are not iterable by default. You must maintain a separate array of keys to iterate over mapping entries.
3. Struct Memory Assignment
Assigning a struct from storage to memory creates a copy. Modifying the memory copy does not update storage.
4. Array Push Cost
push() on storage arrays is expensive (20k+ gas for new elements). Use it sparingly or batch updates.
5. String Comparisons
Strings are dynamic byte arrays and cannot be compared with ==. Use keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)).
Practice Questions
What is the difference between
memoryandcalldata? Both are temporary.memorycan be modified within a function.calldatais read-only and only available for external function parameters.calldatais cheaper.Why can't you iterate over a mapping directly? Mappings are hash tables without stored keys. You cannot enumerate keys without maintaining a separate array of keys alongside the mapping.
What happens when you pass a struct to a function? It depends on the data location.
storagepasses a reference (modifications persist),memorypasses a copy (modifications are temporary), andcalldatais read-only.How do you delete a mapping entry? Use
delete mappingName[key]. This resets the value to its default (zero for integers, empty for strings, zero address for addresses).What is the default value for an uninitialized uint256? Zero. All uninitialized storage variables in Solidity default to their zero-value representation.
Challenge
Build a student gradebook contract using mappings of structs. Each student has a name, ID, and array of grades. Implement functions to add students, record grades, calculate averages, and list all students. Use storage references and avoid unbounded loops.
Real-World Task
Create a decentralized task management system where users create projects with struct-based tasks, assign them to addresses, and track completion status. Use TypeScript with Hardhat to deploy and test the contract. This pattern mirrors real project management dApps on Ethereum.
Frequently Asked Questions
Next Steps
Now that you understand Solidity data types and structures, proceed to Functions, Modifiers and Events for advanced contract patterns. Then explore Smart Contract design patterns for building production-ready decentralized applications.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro