Solidity Cheatsheet

Functions

Use this Solidity reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Function Declaration

function <name>(<params>) <visibility> <mutability> <modifiers> returns (<types>) {
    // body
}
// Full example
function transfer(address to, uint256 amount)
    external
    nonReentrant
    whenNotPaused
    returns (bool success)
{
    balances[msg.sender] -= amount;
    balances[to] += amount;
    return true;
}

Visibility

KeywordWho can call
externaloutside calls only (cheapest for external callers — args stay in calldata)
publicexternal + internal
internalthis contract + derived contracts
privatethis contract only

State Mutability

KeywordReads storageWrites storageReceives ether
(none)yesyesno
viewyesnono
purenonono
payableyesyesyes
function getBalance() external view returns (uint256) {
    return balances[msg.sender];
}

function add(uint a, uint b) external pure returns (uint) {
    return a + b;
}

function deposit() external payable {
    balances[msg.sender] += msg.value;
}

Parameters and Return Values

// Named returns — can use implicit return
function divmod(uint a, uint b) pure returns (uint quotient, uint remainder) {
    quotient  = a / b;
    remainder = a % b;
    // implicit return (no return statement needed)
}

// Tuple destructuring
(uint q, uint r) = divmod(10, 3);

// Multiple return values
function minMax(uint[] calldata arr) external pure returns (uint min, uint max) {
    min = type(uint).max;
    for (uint i; i < arr.length; ++i) {
        if (arr[i] < min) min = arr[i];
        if (arr[i] > max) max = arr[i];
    }
}

// Calldata vs memory for arrays
function processCalldata(uint[] calldata data) external pure returns (uint) { }
function processMemory(uint[] memory data) public pure returns (uint) { }

Prefer calldata over memory for external function array/struct parameters — it avoids the copy and is cheaper.

Function Overloading

function encode(uint256 x) internal pure returns (bytes memory) { }
function encode(address x) internal pure returns (bytes memory) { }
function encode(bytes32 x) internal pure returns (bytes memory) { }

// Called based on argument type — no ambiguity allowed at compile time

virtual and override

contract Base {
    function greet() public virtual returns (string memory) {
        return "hello from Base";
    }
}

contract Child is Base {
    function greet() public virtual override returns (string memory) {
        return "hello from Child";
    }
}

// Multiple inheritance — must list all parents
contract Multi is A, B {
    function greet() public override(A, B) returns (string memory) {
        return super.greet(); // calls most-derived parent (C3 linearization)
    }
}

Constructors

contract Token {
    string public name;
    address public owner;

    constructor(string memory _name) {
        name  = _name;
        owner = msg.sender;
    }
}

// With inheritance
contract MyToken is Ownable, ERC20 {
    constructor(address initialOwner)
        Ownable(initialOwner)
        ERC20("MyToken", "MTK")
    {
        _mint(initialOwner, 1_000_000e18);
    }
}

// No constructor = default (zero-arg, internal for abstract)

Fallback and Receive

contract Wallet {
    // Called when msg.data is empty AND ether is sent
    receive() external payable {
        emit Received(msg.sender, msg.value);
    }

    // Called when no function matches (or receive() is absent)
    fallback() external payable {
        // Can inspect msg.data
    }

    // fallback without payable — reverts if ether is included
    fallback() external { }
}

Call routing order:

  1. If msg.data is empty and ether is sent → receive() (if defined), else fallback()
  2. If msg.data is non-empty → match function selector, else fallback()

Internal Function Calls

contract Calculator {
    function square(uint x) public pure returns (uint) {
        return _mul(x, x);  // internal call — no gas overhead of external call
    }

    function _mul(uint a, uint b) internal pure returns (uint) {
        return a * b;
    }
}

External Calls

// Typed interface call (preferred)
IERC20 token = IERC20(tokenAddress);
token.transfer(to, amount); // reverts on failure

// Low-level call (returns success flag)
(bool ok, bytes memory data) = addr.call{value: 1 ether, gas: 5000}(
    abi.encodeWithSelector(IERC20.transfer.selector, to, amount)
);
require(ok, "call failed");

// delegatecall — runs callee code in caller's context (proxy pattern)
(bool ok2, ) = implementation.delegatecall(msg.data);

// staticcall — read-only external call (like view)
(bool ok3, bytes memory res) = addr.staticcall(abi.encodeWithSignature("get()"));

Always check the return value of low-level calls. call does not revert on failure.

Function Selectors

// 4-byte selector = keccak256("functionName(type1,type2,...)")[:4]
bytes4 sel = IERC20.transfer.selector;
// equivalent to:
bytes4 sel2 = bytes4(keccak256("transfer(address,uint256)"));

// Dispatching manually
function dispatch(bytes calldata data) external {
    bytes4 sig = bytes4(data[:4]);
    if (sig == IERC20.transfer.selector) {
        // ...
    }
}

Free Functions

Defined outside any contract — share no state, always internal.

// MyLib.sol
function hashPair(bytes32 a, bytes32 b) pure returns (bytes32) {
    return a < b ? keccak256(abi.encode(a, b)) : keccak256(abi.encode(b, a));
}

contract MerkleTree {
    function verify(bytes32 a, bytes32 b) external pure returns (bytes32) {
        return hashPair(a, b); // can call free functions directly
    }
}

using ... for (Attached Functions)

library ArrayUtils {
    function contains(uint[] storage arr, uint val) internal view returns (bool) {
        for (uint i; i < arr.length; ++i)
            if (arr[i] == val) return true;
        return false;
    }
}

contract MyContract {
    using ArrayUtils for uint[];
    uint[] private items;

    function has(uint val) external view returns (bool) {
        return items.contains(val); // method-call syntax
    }
}

// Global attachment (0.8.13+)
using ArrayUtils for uint[] global;

Gas Optimization Patterns

// Cache storage reads in memory
function sumItems() external view returns (uint256 total) {
    uint256 len = items.length; // 1 SLOAD
    for (uint i; i < len; ++i)
        total += items[i];
}

// Use ++i (saves ~5 gas vs i++)
for (uint i; i < 10; ++i) { }

// Short-circuit: cheap checks first
function check(address a, uint v) external view returns (bool) {
    return v > 0 && balances[a] >= v; // skip SLOAD if v == 0
}