Solidity Cheatsheet
Mappings and Structs
Use this Solidity reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Mappings
// Basic syntax mapping(KeyType => ValueType) visibility name; mapping(address => uint256) public balances; mapping(bytes32 => bool) public seen; mapping(uint256 => address) public idToOwner;
Key Types
Valid mapping keys: any value type (address, uint, bytes32, bool, enums, user-defined value types).
Not allowed: reference types (bytes, string, struct, arrays).
Nested Mappings
mapping(address => mapping(address => uint256)) public allowance; // ERC-20 pattern: allowance[owner][spender] mapping(address => mapping(uint256 => bool)) public hasVotedOnProposal;
Reading and Writing
// Read — unset keys return the zero value (no key-not-found error) uint256 b = balances[msg.sender]; // 0 if never set // Write balances[msg.sender] = 100; balances[msg.sender] += 50; // Delete — resets to zero value delete balances[msg.sender]; // Nested write allowance[owner][spender] = amount;
Limitations
- Cannot iterate (no internal list of keys — they are hashed via
keccak256). - Cannot copy to/from
memory. - Cannot get
.length. - Cannot be used as function return types directly (unless wrapped in a struct or ABI v2).
Enumerable Pattern
// Track keys manually alongside the mapping mapping(address => uint256) public stakes; address[] public stakers; // enumerable list mapping(address => bool) public isStaker; // existence check O(1) function stake(uint256 amount) external { if (!isStaker[msg.sender]) { stakers.push(msg.sender); isStaker[msg.sender] = true; } stakes[msg.sender] += amount; }
Mapping Storage Slot Calculation
The storage slot for mapping[key] is keccak256(abi.encode(key, slot)) where slot is the mapping's declared slot number. Useful for Foundry stdStorage / off-chain tooling.
Structs
struct Order { address buyer; uint128 price; // pack alongside tokenId in one slot uint128 tokenId; uint256 quantity; // separate slot bool filled; // packs with future fields OrderStatus status; } enum OrderStatus { Open, Filled, Cancelled }
Declaring and Initializing
// Named fields (preferred — order-independent) Order memory o = Order({ buyer: msg.sender, price: 1 ether, tokenId: 42, quantity: 1, filled: false, status: OrderStatus.Open }); // Positional (must match declaration order exactly) Order memory o2 = Order(msg.sender, 1 ether, 42, 1, false, OrderStatus.Open);
Storage vs Memory Pointers
mapping(uint256 => Order) public orders; function fillOrder(uint256 id) external { // Storage pointer — mutations write directly to storage Order storage order = orders[id]; order.filled = true; // 1 SSTORE order.status = OrderStatus.Filled; // Memory copy — mutations do NOT affect storage Order memory snapshot = orders[id]; // copies entire struct snapshot.price = 0; // only changes local copy }
Structs in Mappings and Arrays
mapping(uint256 => Order) public orders; Order[] public orderBook; // Add to array orderBook.push(Order({ buyer: msg.sender, price: 1 ether, /* ... */ })); // Modify via storage ref orderBook[0].filled = true; // Read into memory (cheap for view functions) Order memory o = orderBook[0];
Nested Structs
struct Address { string street; string city; } struct User { string name; Address addr; uint256[] tokenIds; // dynamic array inside struct } mapping(address => User) public users; function setCity(string calldata city) external { users[msg.sender].addr.city = city; }
Returning Structs
// ABI v2 (default since 0.8) allows structs as return types function getOrder(uint256 id) external view returns (Order memory) { return orders[id]; } // Return multiple structs function getOrderAndUser(uint256 id) external view returns (Order memory order, User memory user) { order = orders[id]; user = users[order.buyer]; }
Structs as Mapping Values (Pattern)
struct Stake { uint256 amount; uint40 since; // packed with amount in one slot? no — 256 is full slot // use uint216 + uint40 to pack } // Better packing struct StakePacked { uint216 amount; // bytes 0-26 uint40 since; // bytes 27-31 → same slot! }
Storage Packing
The EVM stores data in 32-byte slots. Fields are packed right-to-left within a slot if they fit.
// BAD — 3 slots (each uint256 gets its own slot) struct Wasteful { uint256 a; uint8 b; uint256 c; } // GOOD — 2 slots (b and d share a slot) struct Efficient { uint256 a; // slot 0 uint8 b; // slot 1, bytes 0 uint8 c; // slot 1, bytes 1 uint240 d; // slot 1, bytes 2-31 (240/8 = 30 bytes fits) uint256 e; // slot 2 }
Rules:
- Order fields from small to large within a "group" to pack tightly.
- A uint256 always starts a new slot.
- Dynamic types (string, bytes, arrays) always occupy their own slot(s).
- Mappings occupy one slot (the actual data is elsewhere via hash).
- bool = 1 byte, packs with adjacent types.
delete Semantics
// Resets to zero value delete balances[msg.sender]; // uint → 0 delete orders[id]; // struct → all fields zeroed delete orderBook; // array → length 0 (expensive if long) // Does NOT free mapping keys (mappings can't be fully deleted) delete allowance[owner]; // only outer key is zeroed; inner slots remain
Hashing Mappings and Structs (EIP-712)
bytes32 constant ORDER_TYPEHASH = keccak256( "Order(address buyer,uint128 price,uint128 tokenId,uint256 quantity)" ); function hashOrder(Order memory o) internal pure returns (bytes32) { return keccak256(abi.encode( ORDER_TYPEHASH, o.buyer, o.price, o.tokenId, o.quantity )); }