Solidity Programming Interview Questions for Blockchain
25 Solidity Programming questions with answers for blockchain roles — 8 easy, 14 medium, 3 hard.
About Solidity Programming
Write smart contracts in Solidity 0.8: types and data locations, visibility, modifiers, mappings, errors, inheritance, interfaces and libraries, receive and fallback, upgradeable proxy patterns and gas optimisation. You build tokens, escrow and voting contracts and test them with Foundry unit and fuzz tests.
Sheet progressStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
Solidity is a statically typed, contract-oriented, curly-brace language for writing smart contracts on Ethereum and other EVM chains. Its syntax resembles JavaScript and C++, and it compiles to EVM bytecode plus an ABI. It supports inheritance, libraries, user-defined types, custom errors and events. Version 0.8.x is current, with checked arithmetic by default. Developers usually write it with Foundry or Hardhat and reuse audited building blocks from OpenZeppelin Contracts.
02
What are value types and reference types in Solidity, and what are data locations?
Value types such as uint, int, bool, address, bytes32 and enums are copied when assigned or passed. Reference types such as arrays, structs, strings and mappings point to data, so you must state a data location: storage for persistent contract state, memory for temporary data inside a function, or calldata for read-only external inputs. Assigning storage to a local storage pointer modifies state, while copying to memory creates an independent copy.
03
Explain public, external, internal and private visibility.
public functions can be called externally and internally, and public state variables get an automatic getter. external functions can only be called from outside, or via this.f(), and read arguments directly from calldata. internal functions and variables are available to the contract and contracts that inherit it. private is limited to the defining contract only. Note that private only restricts other contracts; the data is still fully visible on the blockchain to anyone.
04
What is the difference between view, pure and payable functions?
A view function reads state but does not modify it, so it is free when called off-chain via eth_call. A pure function neither reads nor modifies state and works only on its inputs, for example a math helper. A payable function can receive ETH along with the call; non-payable functions revert if ETH is sent. View and pure functions still cost gas when called inside a state-changing transaction.
Modifiers are reusable pieces of code that run before or after a function body, placed where the underscore symbol appears. They are mainly used for checks such as onlyOwner, whenNotPaused or nonReentrant. For example, modifier onlyOwner() { require(msg.sender == owner, 'not owner'); _; } blocks everyone except the owner. Keep modifiers short and check-only; complex logic inside modifiers hides behaviour and increases bytecode size because the code is inlined into every function that uses it.
06
What is the difference between constant and immutable?
constant variables must be assigned a compile-time value and are inlined into the bytecode, for example uint256 constant MAX = 1000. immutable variables are assigned once in the constructor and then embedded in the deployed code, for example an owner address or token address chosen at deployment. Neither uses a storage slot, so reading them is far cheaper than reading normal state. Use them for anything that never changes after deployment.
Inherit the audited ERC20 implementation and mint the initial supply in the constructor.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ERC20} from '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
contract CampusToken is ERC20, Ownable {
constructor() ERC20('Campus Token', 'CAMP') Ownable(msg.sender) {
_mint(msg.sender, 1_000_000 * 10 ** decimals());
}
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
}
}
08
Tell me about a time you learnt a new technology quickly to meet a deadline.
Situation: in my internship I was asked to add NFT minting to a demo in ten days, and I had never written Solidity. Task: deliver a working, tested contract. Action: I spent two days on Solidity basics and OpenZeppelin docs, built the ERC-721 contract, asked a senior to review my approach early, and wrote Foundry tests for every function. Result: the demo shipped on time on Sepolia, and the review caught only minor issues.
09
How are mappings stored, and why can you not iterate over them?
A mapping does not store keys. The value for key k in a mapping declared at slot p lives at storage slot keccak256(k . p). Every possible key implicitly exists with a default zero value, so the contract cannot know which keys were set or how many. To iterate, keep a separate array of keys or use OpenZeppelin EnumerableSet or EnumerableMap, accepting extra gas, or rebuild the list off-chain from emitted events.
10
What is the difference between require, revert, assert and custom errors?
require(condition, message) validates inputs and conditions, reverting with a message if false. revert stops execution explicitly and is often used with custom errors, such as error InsufficientBalance(uint256 need, uint256 have); revert InsufficientBalance(x, y); which are cheaper than strings and carry data. assert checks invariants that should never fail; a failure signals a bug and raises a Panic. Since Solidity 0.8.26 you can also pass custom errors to require.
11
How does inheritance work in Solidity, including virtual and override?
A contract inherits with is, for example contract Token is ERC20, Ownable. Parent functions that may be changed must be marked virtual, and the child must use override; with several parents you list them, as in override(A, B). Solidity uses C3 linearisation, so the order of the parent list matters, from most base to most derived, and super calls the next contract in that order. Constructors of parents run first in linearised order.
12
Compare interfaces, abstract contracts and libraries.
An interface only declares external functions, events and errors, with no state or implementation; it is used to call other contracts, like IERC20. An abstract contract can have state and implemented functions but leaves at least one unimplemented or is marked abstract, so it cannot be deployed alone. A library holds reusable functions without state; internal library functions are inlined, while external ones are deployed once and called via delegatecall. Libraries can attach to types with using for.
13
What is the difference between receive and fallback functions?
receive() external payable runs when a contract receives plain ETH with empty calldata, for example a transfer from a wallet. fallback() runs when calldata matches no function, or when ETH arrives with data and no receive exists; it must be payable to accept ETH. Proxies use fallback to delegatecall all unknown calls to the implementation. Keep both simple, since a reverting or gas-heavy receive can break senders that use transfer with its 2300 gas stipend.
14
How does arithmetic overflow work in Solidity 0.8, and when do you use unchecked?
Since 0.8.0, arithmetic is checked by default: overflow or underflow reverts with a Panic, so SafeMath is no longer needed. Checks cost a little gas. Wrapping code in an unchecked block disables them, which is safe when you have proven the value cannot overflow, such as a loop counter bounded by an array length, or subtracting after a require that a is at least b. Using unchecked carelessly reintroduces the old overflow bugs.
15
The business wants to change contract logic after launch. How do you plan for it?
Decide early whether upgradeability is needed, because it adds trust and storage risks. Options are a proxy (UUPS or Transparent) controlled by a multisig with a timelock, a parameter-driven design where only settings like fees change through governed setters, or a migration plan to a new immutable version. Whatever you choose, document it for users, protect upgrade rights, keep storage layout append-only, and test each upgrade on a fork before executing.
16
A function that distributes rewards loops over all users and is getting too expensive. How do you fix it?
Unbounded loops eventually exceed the block gas limit. Switch to a pull-based accounting model: maintain a global reward-per-token accumulator that grows when rewards are added, and store each user's last checkpoint. Users claim with reward equals balance times (current accumulator minus their checkpoint). This makes distribution O(1) for any number of users. Alternatively, use a Merkle distributor where rewards are computed off-chain and users claim with proofs.
17
You need to send ETH to hundreds of winners. Should you push payments or let them pull?
Prefer pull payments. If a contract pushes ETH in a loop, one recipient that reverts, perhaps a contract without receive, can block everyone, and the loop may exceed the gas limit. With pull payments, the contract records each winner's amount in a mapping and winners call withdraw, which zeroes their balance before sending. This isolates failures and limits reentrancy risk. Push only for small fixed lists using call with a checked result.
18
Write a Foundry unit test and a fuzz test for a contract.
Foundry tests are Solidity contracts; functions starting with test run as tests, and parameters make them fuzz tests.
import {Test} from 'forge-std/Test.sol';
import {Vault} from '../src/Vault.sol';
contract VaultTest is Test {
Vault v;
function setUp() public { v = new Vault(); }
function testDeposit() public {
v.deposit{value: 1 ether}();
assertEq(v.balanceOf(address(this)), 1 ether);
}
function testFuzzDeposit(uint96 amt) public {
vm.assume(amt > 0);
vm.deal(address(this), amt);
v.deposit{value: amt}();
assertEq(address(v).balance, amt);
}
}
Run with forge test -vvv.
19
Write a simple escrow contract where a buyer deposits and releases payment to a seller.
Situation: a senior reviewer rejected my pull request for a staking feature, saying the storage layout would break future upgrades. Task: fix it and learn. Action: I thanked him, asked him to explain the risk, read about EIP-1967 and storage gaps, rewrote the contract with namespaced storage, and added a storage-layout check to CI. Result: the PR merged, and that check later caught a teammate's layout change before an upgrade.
22
How have you mentored junior developers on a smart contract team?
Situation: our team hired three freshers who knew Solidity syntax but not security. Task: get them productive safely. Action: I created a two-week plan with Ethernaut challenges, pair-programmed their first features, and made them write tests before code. In reviews I explained why, not just what, and kept a shared checklist of common bugs. Result: within two months they shipped features independently, and one found a rounding bug during an internal review.
23
How do upgradeable contracts work, and how do Transparent and UUPS proxies differ?
A proxy holds the state and delegates every call to an implementation contract whose address is stored in a standard EIP-1967 slot; upgrading means pointing to a new implementation. In the Transparent pattern, upgrade logic lives in the proxy and an admin cannot call implementation functions, which avoids selector clashes. In UUPS, upgrade logic lives in the implementation, making proxies cheaper, but a faulty upgrade can brick it. Both use initialisers instead of constructors and require storage-layout compatibility.
24
What is storage packing, and what are other key gas optimisations?
The EVM stores data in 32-byte slots, so consecutive small variables like uint128, uint64, address and bool can share a slot if declared next to each other, saving expensive SSTOREs. Other optimisations: use calldata for external arrays, cache storage reads in local variables, use immutable and constant, prefer custom errors over strings, avoid unbounded loops, use events instead of storing history, and use unchecked for counters that cannot overflow. Always measure with forge snapshot.
25
Your contract fails to deploy because it exceeds the 24 KB size limit. What do you do?
The EIP-170 limit is 24,576 bytes of runtime code. First enable the optimiser and try a lower runs value or via-IR. Replace revert strings with custom errors, move repeated logic out of modifiers into internal functions, and remove unused code. If still too big, split logic into separate contracts or external libraries, or use a modular pattern like the Diamond (EIP-2535). Splitting also often improves clarity and auditability.
Frequently asked questions
How important is Solidity Programming in Blockchain interviews?
Write smart contracts in Solidity 0.8: types and data locations, visibility, modifiers, mappings, errors, inheritance, interfaces and libraries, receive and fallback, upgradeable proxy patterns and gas optimisation. You build tokens, escrow and voting contracts and test them with Foundry unit and fuzz tests.
How should I use the Solidity Programming sheet?
Go top to bottom — easy to hard. Answer before opening each answer, tick what you solved and save misses to Last-Minute Revision.