Anatomy of a Solidity Function
A Solidity function declaration bundles several independent concerns into one line: the name and parameters, a visibility specifier (public, external, internal, or private), an optional state-mutability keyword (view, pure, or payable), optional custom modifiers, and an optional returns clause. Unlike many languages, visibility is mandatory — omitting it is a compile error since Solidity 0.5. Getting the visibility right is a security decision, because a function accidentally left public can let anyone call privileged logic.
Cricket analogy: A team sheet lists each player's role, batting order, and whether they bowl — a Solidity function signature packs name, visibility, and mutability into one compact declaration the same way.
The Four Visibility Levels
public functions can be called both internally and from outside the contract, and the compiler generates a getter for public state variables. external functions can only be called from outside (or via this.f()), and they read arguments directly from calldata, which makes them cheaper for large array parameters. internal functions are callable only within the contract and by contracts that inherit from it — this is the default for functions without an explicit label being disallowed, but internal is the visibility inherited functions rely on. private functions are callable only within the exact contract that defines them and are not visible to derived contracts.
Cricket analogy: public is like the public gate anyone enters, external the players' entrance from outside only, internal the dressing room shared by the squad and its academy, and private the captain's locked locker.
Rule of thumb: use external for functions meant to be called only from outside (especially with large array/bytes arguments, since calldata is cheaper than memory), public when a function must be callable both internally and externally, internal for shared helpers used by the contract and its children, and private for helpers that should never be overridden or seen by subclasses.
State Mutability: view, pure, and payable
State-mutability keywords declare how a function interacts with the blockchain state. A view function promises not to modify state but may read it; a pure function promises to neither read nor modify state, depending only on its arguments; and a payable function is allowed to receive Ether via msg.value. If none is given, the function may modify state. The compiler enforces these promises: writing to a storage variable inside a view function is a compile error. Calling a view or pure function externally (as an eth_call, not a transaction) costs the caller no gas because nothing is mined.
Cricket analogy: A view function is like the third umpire reviewing footage — reading the state but never changing the score; a pure function is a run-rate calculator using only the numbers given, touching no live match data.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Bank {
mapping(address => uint256) private balances; // private state
// payable: can receive Ether via msg.value
function deposit() external payable {
balances[msg.sender] += msg.value;
}
// view: reads state, never writes it
function balanceOf(address who) public view returns (uint256) {
return balances[who];
}
// pure: depends only on inputs, touches no state
function feeFor(uint256 amount) public pure returns (uint256) {
return (amount * 3) / 1000; // 0.3% fee
}
// external: cheaper for calldata; only callable from outside
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount, "insufficient");
balances[msg.sender] -= amount; // effects before interaction
payable(msg.sender).transfer(amount);
}
// internal: reusable helper for this contract and its children
function _netAmount(uint256 amount) internal pure returns (uint256) {
return amount - feeFor(amount);
}
}Marking a function public when it changes privileged state and forgetting to add an access-control modifier is one of the most common critical vulnerabilities. Anyone can call a public function. Restrict sensitive functions with a modifier like onlyOwner, and prefer the least-permissive visibility that still works.
Visibility also interacts with inheritance. A private function cannot be overridden or even seen by a child contract, whereas an internal function can be called and, if marked virtual, overridden by children. Public and external functions form the contract's ABI — the interface other contracts and off-chain clients call. Because external functions are absent from the internal call graph, calling one from within the same contract requires this.f(), which performs an actual message call and costs more gas than an internal call would.
Cricket analogy: An internal coaching drill can be adapted by the academy team (overridden if virtual), but the captain's private superstition routine stays with him alone and no junior inherits it.
- Visibility is mandatory in Solidity — every function must be public, external, internal, or private.
- public: callable inside and outside; external: outside only (cheaper for calldata args); internal: this contract and children; private: this contract only.
- view reads but never writes state; pure neither reads nor writes; payable can receive Ether via msg.value.
- The compiler enforces mutability promises — writing state in a view function is a compile error.
- view/pure calls made as eth_call cost the caller no gas; state-changing calls require a mined transaction.
- Overly permissive visibility (public without access control) is a top security risk — use the least-permissive level.
- private functions cannot be overridden or seen by child contracts; internal virtual functions can be overridden.
Practice what you learned
1. Which visibility level makes a function callable only from outside the contract (or via this.f())?
2. What does declaring a function `pure` promise?
3. Why can a private function NOT be called by a contract that inherits from the defining contract?
4. What keyword must a function have to accept Ether via msg.value?
5. Why is `external` often cheaper than `public` for a function taking a large array argument?
Was this page helpful?
You May Also Like
Function Modifiers in Solidity
Function modifiers are reusable pieces of code that wrap function bodies to enforce preconditions like access control, using the special _ placeholder to mark where the function runs.
Conditionals and Loops in Solidity
Learn how Solidity handles branching with if/else and repetition with for, while, and do-while loops, and why unbounded loops are dangerous in a gas-metered environment.
Error Handling with require, revert, assert
Solidity handles errors by reverting state changes. Learn when to use require for input validation, revert with custom errors for gas-efficient failures, and assert for invariants that must never fail.
Events and Logging
Events emit indexed logs to the blockchain that off-chain applications can efficiently query, providing a cheap way to record activity that smart contracts themselves cannot read back.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics