0
0
Blockchain / Solidityprogramming~15 mins

Visibility modifiers (public, private, internal, external) in Blockchain / Solidity - Deep Dive

Choose your learning style9 modes available
Overview - Visibility modifiers (public, private, internal, external)
What is it?
Visibility modifiers in Solidity control who can access functions and variables in a smart contract. They define whether a function or variable can be used inside the contract, by derived contracts, or by external callers. The main modifiers are public, private, internal, and external, each setting different access rules. This helps protect data and control how contracts interact.
Why it matters
Without visibility modifiers, anyone could call or change any part of a smart contract, risking security and unintended behavior. They help developers protect sensitive data and functions, ensuring only the right users or contracts can interact with them. This is crucial in blockchain where contracts handle valuable assets and must be secure.
Where it fits
Learners should first understand basic Solidity syntax and smart contract structure. After mastering visibility, they can learn about inheritance, function overriding, and security best practices. Later topics include access control patterns and advanced contract interactions.
Mental Model
Core Idea
Visibility modifiers are like doors with different locks controlling who can enter or use parts of a smart contract.
Think of it like...
Imagine a house with rooms: some rooms are open to everyone (public), some only to family members (internal), some locked tight for only the owner (private), and some only accessible from outside the house through a special door (external).
┌───────────────┐
│   Contract    │
│ ┌───────────┐ │
│ │ private   │ │  ← Only inside this contract
│ ├───────────┤ │
│ │ internal  │ │  ← Inside this contract and derived contracts
│ ├───────────┤ │
│ │ public    │ │  ← Anyone can access
│ └───────────┘ │
│               │
│ external func │  ← Only callable from outside the contract
└───────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding basic visibility modifiers
🤔
Concept: Introduce the four visibility modifiers and their basic meanings.
In Solidity, functions and variables can have one of four visibility modifiers: - public: accessible everywhere - private: accessible only inside the contract - internal: accessible inside the contract and contracts that inherit it - external: accessible only from outside the contract Example: contract Example { uint private secret; uint internal shared; uint public open; function externalFunc() external {} function publicFunc() public {} function internalFunc() internal {} function privateFunc() private {} }
Result
You can see which parts of the contract are accessible from where based on these modifiers.
Understanding these four modifiers is the foundation for controlling access and protecting contract data.
2
FoundationHow visibility affects variables
🤔
Concept: Variables also have visibility that controls who can read or write them.
Variables marked private cannot be read or modified outside the contract. Internal variables can be accessed by derived contracts. Public variables automatically get a getter function allowing anyone to read them. External does not apply to variables. Example: contract Data { uint private secretNumber = 42; uint internal sharedNumber = 7; uint public openNumber = 100; }
Result
Only openNumber can be read from outside; secretNumber is hidden; sharedNumber is accessible to child contracts.
Knowing variable visibility helps protect sensitive data and control what others can see or change.
3
IntermediateCalling functions with different visibility
🤔Before reading on: Do you think external functions can be called from inside the same contract? Commit to your answer.
Concept: Explore how functions with different visibility can be called from inside and outside the contract.
Public functions can be called both internally and externally. External functions can only be called from outside the contract, not internally by other functions in the same contract. Internal and private functions can only be called inside the contract or derived contracts (internal) or only inside the contract (private). Example: contract CallTest { function externalFunc() external {} function publicFunc() public {} function internalFunc() internal {} function privateFunc() private {} function testCalls() public { publicFunc(); // works internalFunc(); // works privateFunc(); // works // externalFunc(); // ERROR: cannot call external function internally this.externalFunc(); // works but uses external call } }
Result
External functions cannot be called directly inside the contract but can be called via this.functionName().
Understanding how external functions differ in calling context prevents common bugs and clarifies contract interaction.
4
IntermediateInheritance and internal visibility
🤔Before reading on: Can a derived contract access private variables of its parent? Commit to your answer.
Concept: Internal visibility allows access in derived contracts, but private does not.
When a contract inherits another, it can access internal functions and variables of the parent but not private ones. Example: contract Parent { uint private secret = 1; uint internal shared = 2; } contract Child is Parent { function readShared() public view returns (uint) { return shared; // works } // function readSecret() public view returns (uint) { // return secret; // ERROR: private not accessible // } }
Result
Derived contracts can use internal members but not private ones.
Knowing this helps design contracts with proper encapsulation and inheritance.
5
AdvancedWhy use external functions for gas optimization
🤔Before reading on: Do you think external functions are cheaper to call than public ones? Commit to your answer.
Concept: External functions can be more gas efficient when called externally because they read calldata directly.
External functions receive arguments from calldata, which is cheaper than copying to memory as public functions do. This makes external functions better for large arrays or data passed from outside. Example: contract GasTest { function externalFunc(uint[] calldata data) external {} function publicFunc(uint[] memory data) public {} } Calling externalFunc with large arrays costs less gas than publicFunc.
Result
External functions save gas when called from outside with large data inputs.
Understanding gas costs guides better contract design and optimization.
6
AdvancedLimitations of private and internal visibility
🤔
Concept: Private and internal do not prevent reading data from the blockchain storage externally.
Even private variables are stored on the blockchain and can be read by anyone with blockchain tools. Visibility only controls access inside the contract code, not data privacy on the blockchain. Example: contract Secret { uint private secretData = 123; } Anyone can read secretData by inspecting the blockchain state directly.
Result
Private does not mean data is secret from the public blockchain.
Knowing this prevents false assumptions about privacy and encourages encryption or off-chain secrets.
7
ExpertSurprising behavior of external calls inside contracts
🤔Before reading on: Does calling an external function via this.func() inside a contract use the same gas as a direct internal call? Commit to your answer.
Concept: Calling external functions via this.func() inside the same contract triggers a full external call, which is more expensive than internal calls.
When you call an external function from inside the contract using this.func(), it uses a message call, like an external transaction, costing more gas and changing context. Example: contract Example { function externalFunc() external {} function callExternal() public { this.externalFunc(); // external call, higher gas } function callInternal() public { externalFunc(); // ERROR: cannot call external internally } }
Result
Using this.func() for external calls inside contracts is costly and changes execution context.
Understanding this prevents inefficient code and unexpected behavior in contract design.
Under the Hood
Visibility modifiers control how the Solidity compiler generates code and access rules. Private and internal members are compiled so they cannot be accessed by other contracts' code, enforced by the compiler and EVM bytecode. Public variables get automatic getter functions. External functions are compiled to expect calldata inputs and can only be called via message calls from outside or via this.func() internally, which triggers an external call. However, all data is stored on the blockchain and readable by anyone, so visibility only controls code-level access, not blockchain data privacy.
Why designed this way?
Solidity was designed to balance ease of use, security, and gas efficiency. Visibility modifiers help developers write safer contracts by restricting access and clarifying intent. External functions optimize gas for large calldata inputs. Private and internal protect contract logic from accidental misuse or inheritance issues. The design reflects blockchain's transparent nature, so data privacy is not guaranteed by visibility but by cryptography or off-chain methods.
┌─────────────────────────────┐
│        Solidity Compiler     │
├──────────────┬──────────────┤
│ Visibility   │ Code Output  │
├──────────────┼──────────────┤
│ private      │ Internal only│
│ internal     │ Internal +   │
│              │ derived only │
│ public       │ Getter func  │
│ external     │ Calldata func│
└──────────────┴──────────────┘

Call flow:
[External Caller] → (message call) → [external function]
[Contract] → (direct call) → [public/internal/private functions]
[Contract] → (this.func()) → (message call) → [external function]
Myth Busters - 4 Common Misconceptions
Quick: Does marking a variable private keep its value hidden from everyone on the blockchain? Commit to yes or no.
Common Belief:Private variables are completely hidden and secret on the blockchain.
Tap to reveal reality
Reality:Private variables are hidden only in code access but their values are stored publicly on the blockchain and can be read by anyone.
Why it matters:Assuming private means secret can lead to exposing sensitive data unintentionally.
Quick: Can external functions be called internally without any special syntax? Commit to yes or no.
Common Belief:External functions can be called like public functions inside the same contract.
Tap to reveal reality
Reality:External functions cannot be called directly inside the contract; they require this.func() syntax which triggers an external call.
Why it matters:Misusing external functions internally causes compilation errors or unexpected gas costs.
Quick: Does internal visibility allow access from any contract? Commit to yes or no.
Common Belief:Internal functions and variables are accessible by all contracts.
Tap to reveal reality
Reality:Internal members are accessible only inside the contract and contracts that inherit from it, not all contracts.
Why it matters:Misunderstanding this can cause security holes or broken inheritance logic.
Quick: Are public variables writable by anyone? Commit to yes or no.
Common Belief:Public variables can be changed by anyone since they are public.
Tap to reveal reality
Reality:Public variables have public getter functions but can only be changed by functions inside the contract.
Why it matters:Confusing read access with write access can lead to incorrect assumptions about contract security.
Expert Zone
1
External functions use calldata which is cheaper than memory, so they are preferred for large input arrays from outside callers.
2
Calling external functions internally via this.func() triggers a full message call, which is more expensive and changes execution context, unlike internal calls.
3
Private variables are not inherited by child contracts, so they cannot be accessed or overridden, which affects contract design and upgradeability.
When NOT to use
Avoid using external functions for internal logic because of higher gas costs and complexity. Use private or internal for sensitive logic. For data privacy, do not rely on visibility modifiers; instead, use encryption or off-chain storage. When designing libraries or interfaces, prefer public or external as appropriate.
Production Patterns
In production, developers use internal for reusable logic in base contracts, private for sensitive helpers, public for user-facing functions, and external for functions expecting large calldata inputs. They combine visibility with modifiers like onlyOwner for access control. Gas optimization often guides choosing external over public for large data inputs.
Connections
Access Control Patterns
Builds-on
Understanding visibility modifiers is essential before implementing access control like ownership or role-based permissions, which restrict who can call functions.
Object-Oriented Programming Encapsulation
Same pattern
Visibility modifiers in Solidity mirror encapsulation in OOP, controlling access to data and behavior to protect integrity and hide complexity.
Physical Security Systems
Analogy-based
Just like locks and keys control physical access to rooms, visibility modifiers control access to parts of a contract, highlighting the importance of layered security.
Common Pitfalls
#1Assuming private variables keep data secret on blockchain
Wrong approach:contract Secret { uint private secretData = 123; } // Developer thinks secretData is hidden from everyone
Correct approach:// Use encryption or off-chain storage for sensitive data contract Secret { bytes encryptedData; // Store only encrypted data on-chain }
Root cause:Confusing code-level access control with blockchain data privacy.
#2Calling external function internally without this.
Wrong approach:contract Test { function externalFunc() external {} function call() public { externalFunc(); // ERROR } }
Correct approach:contract Test { function externalFunc() external {} function call() public { this.externalFunc(); // works but costly } }
Root cause:Misunderstanding that external functions require external calls even inside the contract.
#3Using public variables expecting write access from outside
Wrong approach:contract Data { uint public value; } // External users try to set value directly
Correct approach:contract Data { uint private value; function setValue(uint _val) public { value = _val; } }
Root cause:Confusing public getter functions with write permissions.
Key Takeaways
Visibility modifiers control who can access functions and variables inside and outside smart contracts.
Public means anyone can read or call; private means only inside the contract; internal means inside and derived contracts; external means only callable from outside.
External functions are optimized for calls from outside and use calldata, saving gas for large inputs.
Visibility does not protect data privacy on the blockchain; all data is publicly readable.
Understanding visibility is essential for secure, efficient, and maintainable smart contract design.