Variables, Data Types & Operators in Solidity
Master Solidity value types, reference types, and operators used in smart contract logic.
Value Types in Solidity
Solidity is a statically-typed language, meaning every variable must have its type declared at compile time. The most fundamental category is value types, which are always passed by value — when you assign them to another variable or pass them to a function, a copy is created.
The boolean type "bool" holds true or false and is used in conditional logic. Integers come in two flavors: "uint" for unsigned (non-negative) integers and "int" for signed integers. Both can be specified with bit sizes from 8 to 256 in steps of 8 — for example, uint8 stores values from 0 to 255, while uint256 (the default for uint) handles numbers up to 2^256 - 1. Since Solidity 0.8.0, arithmetic operations automatically check for overflow and underflow, reverting the transaction if a result exceeds the type's range.
The "address" type holds a 20-byte Ethereum address. There are two variants: "address" for plain addresses and "address payable" for addresses that can receive ETH via the transfer and send methods. You can access an address's ETH balance with "myAddress.balance" and send ETH with "payableAddress.transfer(amount)".
Fixed-size byte arrays like bytes1, bytes2, up to bytes32 store raw binary data. bytes32 is commonly used for hashes and identifiers because it maps directly to a single EVM storage slot, making it gas-efficient. The "enum" type lets you define a set of named constants, which the compiler represents internally as uint8 values. For example, "enum Status { Active, Paused, Cancelled }" creates three possible states that make your code more readable than using raw numbers.
Reference Types and Data Locations
Reference types in Solidity include arrays, structs, mappings, strings, and the dynamic bytes type. Unlike value types, reference types require you to specify a data location — where the data is stored — because copying large data structures between locations is expensive.
Solidity has three data locations: storage, memory, and calldata. Storage is persistent data written to the blockchain. It survives between function calls and is the most expensive to read from and write to. State variables declared at the contract level always live in storage. Memory is temporary data that exists only during a function's execution and is erased between external function calls. It is cheaper than storage but still costs gas to allocate. Calldata is a read-only, non-modifiable area where function arguments for external functions are stored. It is the cheapest option because no copying occurs.
Dynamic arrays are declared with empty brackets like "uint[] myArray" and support push, pop, and length operations. Fixed-size arrays like "uint[5] fixedArray" have a predetermined length that cannot change. Strings in Solidity are essentially dynamic byte arrays with UTF-8 encoding but have limited built-in operations — you cannot concatenate strings with the + operator natively, and accessing individual characters requires converting to bytes first.
When working with reference types inside functions, you must annotate them with memory, storage, or calldata. A storage reference to a state variable creates a pointer — modifying it changes the original data. A memory copy creates an independent copy that does not affect the original. Understanding these semantics is critical because mistakes in data location can lead to subtle bugs where you think you are modifying persistent state but are actually changing a temporary copy.
Operators and Type Conversions
Solidity supports the standard arithmetic operators: addition (+), subtraction (-), multiplication (*), division (/), and modulo (%). Since Solidity 0.8.0, all arithmetic is checked by default, meaning operations that would overflow or underflow will cause the transaction to revert. If you intentionally want unchecked arithmetic for gas savings, you can wrap operations in an "unchecked { }" block, but only do this when you are certain overflow is impossible.
Comparison operators (==, !=, <, >, <=, >=) work on numeric types and return a bool. Logical operators (&&, ||, !) combine boolean expressions and use short-circuit evaluation — if the first operand of && is false, the second is not evaluated, which matters when the second operand has side effects or costs gas.
Bitwise operators (&, |, ^, ~, <<, >>) operate on the binary representation of integers and are useful for flag manipulation and gas optimization. For instance, storing multiple boolean flags in a single uint256 using bit manipulation is far cheaper than using separate bool variables, since each bool occupies a full 256-bit storage slot.
Type conversions in Solidity can be explicit or implicit. Implicit conversions happen automatically when there is no risk of data loss — for example, uint8 converts to uint16 without issue. Explicit conversions require a cast like "uint8(myUint256)" and can truncate data, so they must be used carefully. Converting between address and uint160 is a common pattern because addresses are internally 160-bit values. The conversion "address(uint160(value))" is frequently used when computing contract addresses or performing low-level operations.