Section 01 · Definition
What is a Solidity mapping with uint256?
A mapping with uint256 as either the key or the value is the standard way to attach a number to an on-chain identifier or to an account.
Quick answer
In one sentence: mapping(uint256 => ValueType) gives each numeric ID its own storage slot, while mapping(address => uint256) gives each account one numeric score — together these two shapes power every voting system, leaderboard, and numeric registry on Ethereum.
uint256 is the default integer type in Solidity. It is 32 bytes wide, unsigned, and covers numbers from 0 to 2256 minus 1. Using it in a mapping works exactly like using an address — the EVM hashes the key with the mapping's storage slot to produce a unique derived slot for each entry.
Two common orientations appear in practice. In the first, uint256 is the key: each token ID, proposal ID, or auction ID gets its own slot. In the second, uint256 is the value: each address gets one number — a balance, a score, a vote weight, a timestamp. You will often see both orientations inside the same contract.
// uint256 as KEY — look up by numeric ID
mapping(uint256 => address) public ownerOf; // ERC721 pattern
mapping(uint256 => string) public tokenURI; // metadata by token ID
mapping(uint256 => uint256) public voteCount; // votes per proposal ID
// uint256 as VALUE — store a number per account
mapping(address => uint256) public balances; // ERC20 pattern
mapping(address => uint256) public lastVoted; // block number of last vote
mapping(address => uint256) public score; // leaderboard pointsSection 02 · The voting pattern
How uint256 mappings power a voting contract
A voting contract needs to count votes per proposal and prevent each address from voting twice. Both needs map cleanly to uint256-based mappings.
The proposal ID is a uint256 counter that increments each time a new proposal is created. The vote count for each proposal is a mapping(uint256 => uint256) — key is the proposal ID, value is the running total of votes it has received.
To prevent double voting, a second mapping tracks the proposal ID a voter has already voted on: mapping(address => uint256) public votedFor. Zero means they have not voted yet; any non-zero value is the proposal ID they chose. This is more gas-efficient than a separate bool mapping because you get both the prevention check and the recorded choice in one storage slot.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract SimpleVote {
uint256 public proposalCount;
// proposal ID => total votes received
mapping(uint256 => uint256) public voteCount;
// voter address => proposal ID they voted for (0 = not voted)
mapping(address => uint256) public votedFor;
// proposal ID => description text
mapping(uint256 => string) public description;
event ProposalCreated(uint256 indexed proposalId, string description);
event Voted(address indexed voter, uint256 indexed proposalId);
function createProposal(string calldata _desc) external returns (uint256) {
proposalCount++;
description[proposalCount] = _desc;
emit ProposalCreated(proposalCount, _desc);
return proposalCount;
}
function vote(uint256 _proposalId) external {
require(_proposalId > 0 && _proposalId <= proposalCount, "invalid proposal");
require(votedFor[msg.sender] == 0, "already voted");
votedFor[msg.sender] = _proposalId;
voteCount[_proposalId]++;
emit Voted(msg.sender, _proposalId);
}
function getVotes(uint256 _proposalId) external view returns (uint256) {
return voteCount[_proposalId];
}
}Section 03 · The leaderboard pattern
Tracking per-account scores with address to uint256
Any situation where each account earns a numeric score over time — points, contributions, reputation — maps cleanly to mapping(address => uint256).
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/// @title Leaderboard
/// @notice Tracks a uint256 score per address. Owner can award points.
/// Anyone can read scores and compare two players.
contract Leaderboard {
address public admin;
// score per player
mapping(address => uint256) public score;
// how many times a player has competed
mapping(address => uint256) public gamesPlayed;
// best single-round score for a player
mapping(address => uint256) public personalBest;
event ScoreAwarded(address indexed player, uint256 points, uint256 newTotal);
constructor() { admin = msg.sender; }
modifier onlyAdmin() {
require(msg.sender == admin, "not admin");
_;
}
/// @notice Award points to a player after a round.
function awardPoints(address _player, uint256 _points) external onlyAdmin {
require(_player != address(0), "zero address");
score[_player] += _points;
gamesPlayed[_player] += 1;
if (_points > personalBest[_player]) {
personalBest[_player] = _points;
}
emit ScoreAwarded(_player, _points, score[_player]);
}
/// @notice Returns true if _a has a higher total score than _b.
function isAheadOf(address _a, address _b) external view returns (bool) {
return score[_a] > score[_b];
}
/// @notice Average score per game for a player. Returns 0 if no games played.
function averageScore(address _player) external view returns (uint256) {
if (gamesPlayed[_player] == 0) return 0;
return score[_player] / gamesPlayed[_player];
}
}Smaller uint types save gas when packed
If you know a score will never exceed 65 535, declare it as uint16 instead of uint256. Solidity packs adjacent small types into the same 32-byte storage slot. A struct with three uint16 fields uses one slot instead of three. For a mapping to a struct, packing can cut write gas by 60 percent or more.
Section 04 · Full contract
Complete example: weighted governance with uint256 mappings
This contract combines proposal creation, weighted voting, and score tracking into one deployable example showing every uint256 mapping pattern.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/// @title WeightedGovernance
/// @notice Each voter has a weight. Votes are counted by weight, not heads.
/// Proposals track: vote-for count, vote-against count, weighted totals.
contract WeightedGovernance {
// ── State ────────────────────────────────────────────────────────
address public governor;
uint256 public proposalCount;
// ── Voter data ───────────────────────────────────────────────────
mapping(address => uint256) public voteWeight; // governance tokens held
mapping(address => uint256) public votedOn; // proposal ID voted on (0 = none)
// ── Proposal data ────────────────────────────────────────────────
mapping(uint256 => string) public title;
mapping(uint256 => uint256) public forWeighted; // total weight FOR
mapping(uint256 => uint256) public againstWeighted; // total weight AGAINST
mapping(uint256 => uint256) public forCount; // raw voter count FOR
mapping(uint256 => uint256) public againstCount; // raw voter count AGAINST
mapping(uint256 => bool) public executed;
mapping(uint256 => uint256) public deadline; // block number deadline
// ── Events ───────────────────────────────────────────────────────
event ProposalCreated(uint256 indexed id, string title, uint256 deadline);
event VoteCast(address indexed voter, uint256 indexed proposalId,
bool support, uint256 weight);
event ProposalExecuted(uint256 indexed id, bool passed);
// ── Constructor ──────────────────────────────────────────────────
constructor() { governor = msg.sender; }
modifier onlyGovernor() {
require(msg.sender == governor, "not governor");
_;
}
// ── Voter management ─────────────────────────────────────────────
/// @notice Assign voting weight to an address (governor only).
function grantWeight(address _voter, uint256 _weight) external onlyGovernor {
require(_voter != address(0), "zero address");
voteWeight[_voter] = _weight;
}
// ── Proposal lifecycle ───────────────────────────────────────────
/// @notice Create a proposal. Voting runs for _durationBlocks blocks.
function createProposal(string calldata _title, uint256 _durationBlocks)
external onlyGovernor returns (uint256)
{
proposalCount++;
title[proposalCount] = _title;
deadline[proposalCount] = block.number + _durationBlocks;
emit ProposalCreated(proposalCount, _title, deadline[proposalCount]);
return proposalCount;
}
/// @notice Cast a vote. Each address may vote on at most one proposal.
function castVote(uint256 _proposalId, bool _support) external {
require(_proposalId > 0 && _proposalId <= proposalCount, "invalid proposal");
require(block.number <= deadline[_proposalId], "voting closed");
require(!executed[_proposalId], "already executed");
require(votedOn[msg.sender] == 0, "already voted");
uint256 weight = voteWeight[msg.sender];
require(weight > 0, "no voting weight");
votedOn[msg.sender] = _proposalId;
if (_support) {
forWeighted[_proposalId] += weight;
forCount[_proposalId] += 1;
} else {
againstWeighted[_proposalId] += weight;
againstCount[_proposalId] += 1;
}
emit VoteCast(msg.sender, _proposalId, _support, weight);
}
/// @notice Execute a proposal after its deadline passes.
function execute(uint256 _proposalId) external onlyGovernor {
require(block.number > deadline[_proposalId], "voting still open");
require(!executed[_proposalId], "already executed");
executed[_proposalId] = true;
bool passed = forWeighted[_proposalId] > againstWeighted[_proposalId];
emit ProposalExecuted(_proposalId, passed);
}
// ── View helpers ─────────────────────────────────────────────────
/// @notice Returns (forWeighted, againstWeighted, forCount, againstCount).
function getResults(uint256 _proposalId)
external view returns (uint256, uint256, uint256, uint256)
{
return (
forWeighted[_proposalId],
againstWeighted[_proposalId],
forCount[_proposalId],
againstCount[_proposalId]
);
}
/// @notice Returns true if the proposal passed (more weighted FOR than AGAINST).
function didPass(uint256 _proposalId) external view returns (bool) {
require(executed[_proposalId], "not yet executed");
return forWeighted[_proposalId] > againstWeighted[_proposalId];
}
}Deploy this contract on Remix. Call grantWeight to give several accounts different weights, then create a proposal and cast votes from each account. Notice that the weighted totals in forWeighted and againstWeighted differ from the raw forCount and againstCount — that is the weighted governance difference.
Section 05 · Common mistakes
Three uint256 mapping mistakes to avoid
These three errors are specific to uint256 mappings and come up in audits regularly.
Using 0 as a valid ID
If your mapping(uint256 => ...) uses IDs that start at 1, the zero key is naturally a sentinel for 'does not exist'. Using IDs that start at 0 breaks that convention — you lose the ability to distinguish 'no entry' from 'entry with ID 0'. Start proposal, token, and record IDs at 1 by incrementing before writing: proposalCount++; title[proposalCount] = ...
Integer overflow on vote counts
Solidity 0.8+ reverts on overflow automatically, so voteCount[id]++ will revert rather than wrap when voteCount hits 2^256 - 1. In practice, you will never hit this for vote counts. But for financial values — balances, staked amounts — make sure your business logic cannot overflow through legitimate inputs. If in doubt, cast to a larger type before arithmetic.
Using block.number for deadlines without accounting for chain differences
deadline[id] = block.number + 7200 assumes roughly 12-second blocks on Ethereum mainnet (about 24 hours). On L2 chains like Base or Arbitrum, block times differ significantly. If you deploy the same contract on multiple chains, use block.timestamp instead of block.number for deadlines, and document the intended duration clearly.
FAQ
Frequently asked questions
What does mapping(uint256 => uint256) do in Solidity?
It maps one unsigned integer to another — typically an ID to a count or a score. The most common use is tracking votes per proposal ID (voteCount[proposalId]++) or tracking staked amounts per position ID. Every unwritten key returns 0 by default.
How does a voting smart contract use mappings in Solidity?
A voting contract typically uses two mappings: mapping(uint256 => uint256) to count votes per proposal ID, and mapping(address => uint256) or mapping(address => bool) to record which proposal a voter has already chosen. The combination prevents double voting and tallies results without looping over all participants.
What is the difference between mapping(address => uint256) and mapping(uint256 => address)?
They answer different questions. mapping(address => uint256) asks 'how much does this account hold?' — used for balances, scores, weights. mapping(uint256 => address) asks 'who owns this ID?' — used for NFT ownership, proposal creators, slot occupants. Both use uint256 but in opposite roles.
Can you use uint8 or uint16 as a mapping key in Solidity?
Yes. Any value type — uint8, uint16, uint32, uint64, uint128, uint256, address, bytes32, bool — can be a mapping key. Solidity pads smaller types to 32 bytes before hashing, so there is no gas saving on the key side. The saving from smaller uint types comes when they appear as values inside packed structs.
How do you find the winning proposal in a Solidity voting contract?
You need to iterate over all proposal IDs and compare their vote counts. Keep a uint256[] proposalIds array alongside the mapping, push each new ID into it on creation, then loop over that array in a view function. The mapping itself cannot be iterated — only the array you maintain in parallel.