Vyper’s Public Getters: How They Work and What to Watch For
When you declare a state variable as `public` in Vyper, the compiler auto‑generates a read‑only getter. This blog explains the mechanics, benefits, limits, and how to use them safely in production.
12 Sept 2025, 02:45 UTC

Why the Auto‑Generated Getter Matters
In Vyper, declaring a state variable with the keyword public instantly gives you a read‑only function that returns the value. It removes boilerplate, guarantees the function signature matches the variable type, and keeps the ABI stable. For many contracts this is a win, but it also introduces subtle risks if you’re not careful.
How Vyper Builds the Getter
When the compiler sees foo: public(uint256), it emits a function named foo() with the following properties:
- Visibility:
public– anyone can call it. - Return type matches the variable’s type.
- Marked as
view– no state change, no gas when called externally. - For mappings, the function accepts the key as an argument and performs a keccak256 lookup internally.
- For fixed‑size byte arrays, the entire array is returned in a single call.
Because the getter is baked into the bytecode, the contract’s deployment size increases by a few hundred bytes per public variable. This cost is usually negligible but can add up in contracts with many public state variables.
Practical Example
Contract Source
# example.vy
# A simple account balance tracker
owner: public(address)
# Mapping from user address to balance
balances: public(HashMap[address, uint256])
# Fixed‑size array of recent deposits
# (only the first 10 elements are exposed via the getter)
recent_deposits: public(uint256[10])
# A private helper – not exposed automatically
private_note: private(string)
Inspecting the ABI
vyper --abi example.vy
# Output includes:
#
# {
# "constant": true,
# "inputs": [],
# "name": "owner",
# "outputs": [{"name": "", "type": "address"}],
# "payable": false,
# "stateMutability": "view",
# "type": "function"
# }
# {
# "constant": true,
# "inputs": [{"name": "", "type": "address"}],
# "name": "balances",
# "outputs": [{"name": "", "type": "uint256"}],
# ...
# }
# {
# "constant": true,
# "inputs": [],
# "name": "recent_deposits",
# "outputs": [{"name": "", "type": "uint256[10]"}],
# ...
# }
#
# Note that there is no function for private_note.
Calling the Getter
Using a web3 library you can call the getter without paying gas because it’s a view function. Below is a generic example using JavaScript.
const abi = /* ABI array from the previous step */;
const contract = new ethers.Contract(address, abi, provider);
// Read the owner
const currentOwner = await contract.owner();
console.log('Owner:', currentOwner);
// Read a specific balance
const alice = '0x1234...';
const aliceBalance = await contract.balances(alice);
console.log('Alice balance:', aliceBalance.toString());
// Read the entire recent_deposits array (expensive if called off‑chain)
const deposits = await contract.recent_deposits();
console.log('Recent deposits:', deposits);
Trade‑offs and Constraints
- Exposure Risk: The getter is
public; any external caller can read the data. Don’t declare sensitive variables aspublicunless you intend to expose them. - Limited Types: Nested mappings, mappings of structs, or dynamic arrays cannot be declared
public. Attempting to do so yields a compile‑time error. - No Overloading: Each public variable must have a unique name. If you need multiple getters with the same name but different arguments, you must write a custom function.
- Bytecode Size: Each getter adds a few hundred bytes to the contract. In contracts with dozens of public variables, this can push deployment gas costs higher.
- No Custom Logic: The generated getter simply returns the stored value. If you need to transform data or enforce access control, you must replace it with a manually written function.
Action Plan for Developers
- Use
publiconly for data that is meant to be publicly readable. Keep private or internal state for sensitive information. - For mappings, remember that the getter requires a key. If you need to expose a range or enumerate keys, implement a separate function and maintain an index.
- When deploying, run
vyper --size example.vyto see the bytecode size and compare it with a version that removes unnecessary public variables. - After any change to a public variable’s type or visibility, re‑generate the ABI and verify that external interfaces (e.g., dApps) still work. ABI changes break existing integrations.
- For complex data structures, write explicit getters. This gives you control over gas usage, data formatting, and access restrictions.
By following these guidelines, you can harness Vyper’s automatic getters for clean, efficient contracts while avoiding common pitfalls.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.