Difficulty, Prerequisites and Context
Difficulty: Intermediate
Prerequisites: Basic programming concepts (variables, functions, control flow). Familiarity with object-oriented programming helps. No prior blockchain knowledge required, but understanding what a blockchain is at a high level will make things smoother.
Big picture: Solidity is the dominant language for writing smart contracts on Ethereum, the largest programmable blockchain. This topic sits at the intersection of programming languages and distributed systems. If you have done any work with classes and objects in Java or Python, the contract model will feel familiar, but the execution environment is radically different: code runs on a global virtual machine, every operation has a cost (gas), and deployed code cannot be changed. Understanding Solidity fundamentals is the entry point for everything else in decentralised application development.
Solidity is a typed, compiled language for writing self-executing programs (smart contracts) that live on the Ethereum blockchain. Contracts hold state variables, functions, events and modifiers, and once deployed they are immutable. The language provides familiar data types (booleans, integers, strings, arrays, structs, mappings) alongside blockchain-specific constructs such as visibility modifiers, the pragma version directive, and gas-aware data locations.
Solidity
A high-level, statically typed programming language designed specifically for writing smart contracts on the Ethereum blockchain. Think of it as "the JavaScript of Ethereum" in terms of its role, though the syntax leans closer to C++ and JavaScript combined.
Smart contract
A self-executing program stored on a blockchain that automatically enforces the rules coded into it. In simple terms, it is a program that runs itself once its conditions are met, with no middleman needed.
Ethereum Virtual Machine (EVM)
The runtime environment that executes smart contract bytecode on the Ethereum network. Think of it as the shared computer that every Ethereum node runs, so your contract executes identically everywhere.
Immutable (in the context of deployed contracts)
Once a smart contract is deployed to the blockchain, its code cannot be changed. In simple terms, you cannot patch a live contract the way you would update a web app.
Decentralised
Operating without a central authority. No single server or company controls the contract's execution or data.
Pragma directive
A compiler instruction at the top of a Solidity file that declares which compiler versions the code is compatible with. Think of it as a version lock that stops your code from compiling on an incompatible compiler.
Contract (Solidity construct)
The fundamental building block in Solidity, analogous to a class in object-oriented programming. It bundles state variables, functions, events and modifiers at a specific blockchain address.
State variable
A variable whose value is permanently stored on the blockchain as part of a contract's persistent state. In simple terms, it is the contract's long-term memory.
Visibility modifier
A keyword (public, internal, private, external) that controls which other contracts or accounts can access a variable or function.
dApp (decentralised application)
An application built on top of smart contracts, combining a traditional front-end with blockchain-based back-end logic.
Mapping
A key-value data structure in Solidity, similar to a hash map or dictionary. Used for efficient lookups, such as tracking which address holds which balance.
Struct
A custom data type that groups several variables of different types under one name. Think of it as a record or a row in a database table.
Gas
The unit of computational cost on Ethereum. Every operation in a smart contract costs gas, paid in ETH by the caller.
Every Solidity file starts with a pragma line declaring compiler compatibility.
pragma solidity ^0.8.0; means "compile with 0.8.0 or any later patch/minor version below 0.9.0."
The caret (^) is the most common operator. It pins the major.minor range while allowing patches.
Without a pragma, the compiler may accept any version, risking silent breaking changes.
A contract is declared with the contract keyword and lives at a specific address on Ethereum.
Contracts are analogous to classes in OOP: they encapsulate state and behaviour.
State variables store persistent data on-chain.
Functions perform logic and manipulate state.
Events emit logs for off-chain listeners.
Modifiers add reusable preconditions to functions.
Example skeleton:
contract Ballot {
// state variables, functions, events, modifiers
}State variables are stored permanently in contract storage (on the blockchain).
Three visibility levels control access:
public – auto-generates a getter function; readable by anyone.
internal – accessible inside the contract and by contracts that inherit from it. This is the default if no keyword is specified.
private – accessible only within the declaring contract. Note: "private" does not mean the data is hidden on-chain; it only restricts Solidity-level access.
Example:
uint public totalVotes;
uint internal ownerCount;
uint private secretCount;Boolean: bool – true or false.
Integers: int (signed) and uint (unsigned), in sizes from 8 to 256 bits. uint256 is the most common.
Strings: string for variable-length UTF-8 text.
Bytes: Fixed-size (bytes1 through bytes32) and dynamic (bytes). Fixed bytes are cheaper in gas.
Address: address holds a 20-byte Ethereum address. address payable can receive ETH.
Example declarations:
bool isActive = true;
int256 signedNumber = -5;
uint256 unsignedNumber = 10;
string public message = "Hello";
bytes32 dataHash = 0xabc123...;Fixed-size: length set at compile time. uint[3] public fixedArray = [1, 2, 3];
Dynamic: length can change at runtime. uint[] public dynamicArray;
Key operations on dynamic arrays:
push(element) – appends to the end.
pop() – removes the last element.
.length – returns the current size.
Arrays are not natively iterable; you must use an explicit for loop.
Structs group multiple variables of different types into one composite type.
Useful for modelling real-world entities: voters, proposals, transactions.
Example:
struct Person {
string name;
uint age;
}
Person public person1 = Person("Alice", 30);A mapping is a key-value store: mapping(keyType => valueType).
Commonly used for balances: mapping(address => uint256) public balances;
Key facts:
Mappings are not iterable. You cannot loop through all keys or count how many entries exist.
All possible keys exist by default; unmapped keys return the type's zero value (0, false, empty string).
Mappings can only live in storage, not in memory or as function parameters (with some exceptions for internal functions).
Mappings are the backbone of every ERC-20 token contract: each address maps to a token balance, which is how wallets show you "how much" of a token you hold. Structs model voter profiles in on-chain governance systems such as Compound or MakerDAO. The pragma directive matters in practice because Solidity versions before 0.8.0 allowed silent integer overflow, while 0.8.0+ reverts on overflow by default, so pinning the compiler version is a safety-critical decision in production contracts.
Students often think private state variables are invisible on the blockchain. They are not. Anyone can read raw storage slots; private only prevents other Solidity contracts from accessing the variable through the compiler's type system.
Students often confuse uint and int. uint (unsigned) cannot hold negative numbers. If you subtract a larger number from a smaller uint in Solidity 0.8+, the transaction reverts rather than wrapping around.
Students sometimes assume mappings can be looped over like arrays. They cannot. If you need to iterate, maintain a separate array of keys alongside the mapping.
Students sometimes believe that a struct declaration creates an instance. It does not. You must explicitly instantiate a struct (e.g. Person("Alice", 30)) after declaring the type.
⚠️ Know the three visibility keywords for state variables (public, internal, private) and what each one permits. Expect a question asking which modifier auto-generates a getter.
⚠️ Be able to explain what the caret (^) does in pragma solidity ^0.8.0; and what version range it allows.
⚠️ Understand that mappings return the zero value for unmapped keys, not an error or null.
⚠️ Be ready to compare arrays and mappings: arrays are ordered and iterable; mappings are unordered, non-iterable, and offer O(1) lookup.
⚠️ Expect code-reading questions where you identify data types and predict storage behaviour.
True or false: A private state variable's value is completely hidden from everyone outside the contract. (False. The data is visible on-chain; private only restricts Solidity-level access.)
Fill in the blank: pragma solidity ^0.8.0; allows compilation with any version >= 0.8.0 and < ____. (0.9.0)
True or false: You can use a for loop to iterate over all keys in a mapping. (False. Mappings are not iterable.)
Fill in the blank: The ____ keyword on a state variable tells the compiler to auto-generate a getter function. (public)
True or false: uint256 can store negative numbers. (False. Use int256 for signed integers.)
Q: What does the caret (^) symbol mean in pragma solidity ^0.8.0;?
A: It specifies that the contract is compatible with compiler version 0.8.0 and any later minor or patch version up to, but not including, 0.9.0.
Q: Explain the difference between public, internal, and private visibility for state variables.
A: public auto-generates a getter so anyone (external accounts and other contracts) can read the value. internal restricts access to the current contract and contracts that inherit from it. private restricts access to the current contract only. None of these hide the underlying data from the blockchain itself.
Q: Why are mappings described as "not iterable"? What is the workaround?
A: Solidity mappings do not store a list of their keys, so there is no built-in way to loop through all entries. The common workaround is to maintain a separate array of keys alongside the mapping.
Q: Given the following declaration, what value does balances[0x123...] return if no deposit has been made to that address? mapping(address => uint256) public balances;
A: It returns 0 (the default value for uint256). Mappings return the zero value of the value type for any key that has not been explicitly set.
Q: What is a struct in Solidity, and when would you use one?
A: A struct is a user-defined composite type that groups variables of different types. You would use one when modelling an entity with multiple properties, such as a Voter with a weight, a voted flag, and a vote index.
This material connects directly to Part 2 of these study notes, which covers functions, inheritance, events, error handling and the Ballot contract workflow. Understanding data types and visibility here is prerequisite to understanding function modifiers (pure, view, payable) and how msg.sender interacts with mappings in Part 2. The concept of storage vs. memory (covered in Part 2) builds on knowing which data types live where.
More broadly, Solidity's contract model maps onto object-oriented programming concepts from earlier in the course. If you understand classes, constructors and inheritance in Java or Python, the same mental model applies here, with the added constraint that execution costs gas and deployed state is permanent.
Solidity, smart contract, Ethereum, EVM, Ethereum Virtual Machine, blockchain programming, pragma directive, pragma solidity, contract structure, state variable, visibility modifier, public, internal, private, data types Solidity, uint, int, bool, string, bytes, bytes32, address, mapping, struct, array, dynamic array, fixed array, push, pop, dApp, decentralised application, immutable contract, gas cost, hash map Solidity, key-value store, Purdue CS, Foundations of Computer Science