Conditionals
Solidity supports standard if/else syntax.
function classify(uint256 amount) public pure returns (string memory) {
if (amount == 0) {
return 'zero';
} else if (amount < 100) {
return 'small';
} else {
return 'large';
}
}Loops
Solidity supports for, while, and do...while loops.
function sumArray(uint256[] memory values) public pure returns (uint256 total) {
for (uint256 i = 0; i < values.length; i++) {
total += values[i];
}
}Be careful with loops in production contracts. Each iteration costs gas. If an attacker can make your array unboundedly large, they can cause your function to run out of gas and revert — a denial-of-service attack. Prefer mappings over arrays for large data sets.
require, revert, and assert
These are Solidity's primary error-handling tools. Getting them right is critical for both security and UX.
require
The workhorse of input validation. Reverts the transaction with an error message if the condition is false. Any gas spent up to that point is consumed; unused gas is refunded.
function withdraw(uint256 amount) public {
require(amount > 0, 'Amount must be positive');
require(balances[msg.sender] >= amount, 'Insufficient balance');
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}revert
Explicitly reverts with a custom error. More flexible than require — useful for complex conditionals or when you want to use custom error types (more gas-efficient than string messages).
error InsufficientBalance(uint256 requested, uint256 available);
function withdraw(uint256 amount) public {
if (balances[msg.sender] < amount) {
revert InsufficientBalance(amount, balances[msg.sender]);
}
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}assert
Used to check for internal invariants — conditions that should never be false if the contract logic is correct. Unlike require, a failed assert consumes all remaining gas. Use it sparingly.
function split(uint256 total, uint256 a, uint256 b) internal pure {
assert(a + b == total); // This should always hold — if not, something is very wrong
}try/catch
When calling external contracts, use try/catch to handle failures gracefully without reverting your whole transaction.
interface IExternalContract {
function riskyCall() external returns (bool);
}
function safeCall(address target) public returns (bool success) {
try IExternalContract(target).riskyCall() returns (bool result) {
return result;
} catch {
// External call failed — handle gracefully
return false;
}
}