# Solidity Mappings को समझें — बेसिक से एडवांस पैटर्न तक
Mappings Solidity का सबसे fundamental data structure है, लेकिन इसकी limitations और best practices को समझना जरूरी है। इस tutorial में हम basic से advanced patterns तक सब कुछ cover करेंगे।
Mappings क्या हैं?
Mapping एक key-value store है जो hash table की तरह काम करता है। यह किसी भी built-in या custom type को दूसरे type से map कर सकता है।
// Basic syntax
mapping(KeyType => ValueType) public myMapping;
// Examples
mapping(address => uint256) public balances;
mapping(uint256 => address) public tokenOwners;
mapping(bytes32 => bool) public isValid;
Mappings की Key Features
1. किसी भी key का default value zero होता है:
contract MappingDefaults {
mapping(address => uint256) public balances;
function checkDefault() public view returns (uint256) {
// यह 0 return करेगा, भले ही कभी set न किया गया हो
return balances[address(0x123)];
}
}
2. Mappings iterable नहीं हैं:
आप mapping के सभी keys या values को loop नहीं कर सकते। इसके लिए separate array maintain करना पड़ता है (नीचे देखें)।
3. Storage-only data location:
Mappings सिर्फ state variables के रूप में exist कर सकते हैं, memory या calldata में नहीं।
Nested Mappings
Complex data structures के लिए nested mappings बेहद useful हैं:
contract TokenAllowances {
// owner => spender => amount
mapping(address => mapping(address => uint256)) public allowances;
function approve(address spender, uint256 amount) public {
allowances[msg.sender][spender] = amount;
}
function transferFrom(
address owner,
address recipient,
uint256 amount
) public {
require(allowances[owner][msg.sender] >= amount, "Insufficient allowance");
allowances[owner][msg.sender] -= amount;
// transfer logic...
}
}
Triple Nested Example
contract MultiSigWallet {
// owner => txId => hasConfirmed
mapping(address => mapping(uint256 => bool)) public confirmations;
// या और complex:
// chainId => contractAddress => selector => isAllowed
mapping(uint256 => mapping(address => mapping(bytes4 => bool))) public permissions;
}
Iterable Mapping Pattern
यह सबसे common advanced pattern है। जब आपको mapping iterate करनी हो, तो एक array maintain करें:
contract IterableMapping {
struct User {
uint256 balance;
bool exists;
}
mapping(address => User) public users;
address[] public userList;
function addUser(address user, uint256 balance) public {
// Duplicate prevention
if (!users[user].exists) {
users[user] = User(balance, true);
userList.push(user);
} else {
users[user].balance = balance;
}
}
function removeUser(address user) public {
require(users[user].exists, "User doesn't exist");
// Remove from mapping
delete users[user];
// Remove from array (swap with last and pop)
for (uint256 i = 0; i < userList.length; i++) {
if (userList[i] == user) {
userList[i] = userList[userList.length - 1];
userList.pop();
break;
}
}
}
function getAllUsers() public view returns (address[] memory) {
return userList;
}
function getTotalBalance() public view returns (uint256 total) {
for (uint256 i = 0; i < userList.length; i++) {
total += users[userList[i]].balance;
}
}
}
Gas Optimization Patterns
Pattern 1: Storage Caching
// ❌ Expensive (3 SLOAD operations)
function badExample(address user) public view returns (uint256) {
return balances[user] + balances[user] + balances[user];
}
// ✅ Optimized (1 SLOAD)
function goodExample(address user) public view returns (uint256) {
uint256 balance = balances[user];
return balance + balance + balance;
}
Pattern 2: Packed Structs in Mappings
contract PackedMapping {
struct UserData {
uint128 balance; // 16 bytes
uint64 lastUpdate; // 8 bytes
uint32 reputation; // 4 bytes
uint16 level; // 2 bytes
bool isActive; // 1 byte
// Total: 31 bytes = 1 storage slot!
}
mapping(address => UserData) public users;
// Single SSTORE updates all fields in one slot
function updateUser(address user, uint128 bal, uint32 rep) public {
UserData storage data = users[user];
data.balance = bal;
data.reputation = rep;
data.lastUpdate = uint64(block.timestamp);
}
}
Pattern 3: Mapping vs Array
// Mapping: O(1) access, but can't iterate
mapping(uint256 => address) public owners;
// Array: O(n) search, but can iterate
address[] public ownerList;
// Best of both worlds:
mapping(uint256 => address) public tokenOwner;
mapping(address => uint256[]) public ownerTokens;
Advanced Pattern: EnumerableMap
OpenZeppelin जैसे pattern जो mapping + array combine करते हैं:
library EnumerableMap {
struct Map {
bytes32[] keys;
mapping(bytes32 => uint256) values;
mapping(bytes32 => uint256) indexes; // 1-based index
}
function set(Map storage map, bytes32 key, uint256 value) internal {
if (map.indexes[key] == 0) {
map.keys.push(key);
map.indexes[key] = map.keys.length;
}
map.values[key] = value;
}
function remove(Map storage map, bytes32 key) internal {
uint256 index = map.indexes[key];
require(index > 0, "Key doesn't exist");
uint256 lastIndex = map.keys.length;
bytes32 lastKey = map.keys[lastIndex - 1];
map.keys[index - 1] = lastKey;
map.indexes[lastKey] = index;
map.keys.pop();
delete map.values[key];
delete map.indexes[key];
}
function length(Map storage map) internal view returns (uint256) {
return map.keys.length;
}
}
Real-World Example: DEX Order Book
contract SimpleOrderBook {
struct Order {
address trader;
uint256 amount;
uint256 price;
bool isBuy;
uint256 timestamp;
}
mapping(uint256 => Order) public orders;
mapping(address => uint256[]) public userOrders;
uint256 public nextOrderId;
uint256[] public buyOrders;
uint256[] public sellOrders;
function placeOrder(uint256 amount, uint256 price, bool isBuy) public {
uint256 orderId = nextOrderId++;
orders[orderId] = Order({
trader: msg.sender,
amount: amount,
price: price,
isBuy: isBuy,
timestamp: block.timestamp
});
userOrders[msg.sender].push(orderId);
if (isBuy) {
buyOrders.push(orderId);
} else {
sellOrders.push(orderId);
}
}
function getUserOrders(address user)
public
view
returns (Order[] memory)
{
uint256[] memory orderIds = userOrders[user];
Order[] memory result = new Order[](orderIds.length);
for (uint256 i = 0; i < orderIds.length; i++) {
result[i] = orders[orderIds[i]];
}
return result;
}
}
Common Pitfalls
1. Delete की Limitation
struct Data {
uint256 value;
uint256[] items;
}
mapping(address => Data) public userData;
// ❌ यह nested array को delete नहीं करता
delete userData[msg.sender];
// ✅ Manual cleanup needed
delete userData[msg.sender].items;
delete userData[msg.sender];
2. Existence Check
// ❌ यह काम नहीं करता (0 valid value हो सकता है)
if (balances[user] != 0) { ... }
// ✅ Explicit exists flag
mapping(address => bool) public exists;
mapping(address => uint256) public balances;
Gas Cost Comparison
| Operation | Gas Cost | Notes |
|-----------|----------|-------|
| First SSTORE (zero to non-zero) | ~20,000 | Expensive |
| SSTORE (non-zero to non-zero) | ~5,000 | Update |
| SLOAD | ~2,100 | Read |
| Mapping access | 0 | सिर्फ offset calculation |
Conclusion
Mappings Solidity में data management का backbone हैं। Key takeaways:
- Default values और iteration limitations समझें
- Iterable pattern के लिए array + mapping combine करें
- Gas optimization के लिए storage packing use करें
- Complex use cases में EnumerableMap जैसे libraries consider करें
अगली बार जब mapping use करें, तो सोचें: क्या मुझे iteration चाहिए? क्या struct packing से gas बचा सकते हैं? यही difference है basic और production-ready code में।