# Top 30 Web3 Interview Questions for Solidity Developers
After interviewing dozens of candidates and reviewing hiring practices at top Web3 companies, here are the 30 questions that appear most frequently — with answers that will help you stand out.
Solidity Basics (Questions 1-8)
1. What's the difference between memory, storage, and calldata?
Answer:
- Storage: Persistent data stored on the blockchain. Most expensive. State variables use storage by default.
- Memory: Temporary data that exists during function execution. Erased between external function calls. Cheaper than storage.
- Calldata: Read-only temporary data for function parameters. Cheaper than memory. Only available for external function parameters.
Example:
contract DataLocations {
uint[] public storageArray; // storage by default
function processArray(uint[] calldata data) external {
// data is in calldata (read-only, cheapest)
uint[] memory tempArray = new uint[](data.length); // memory
storageArray = tempArray; // copies to storage
}
}
Follow-up they might ask: "Why can't you use calldata for return values?"
Answer: Calldata is input-only. Return values use memory.
---
2. Explain the difference between transfer, send, and call for sending ETH.
Answer:
transfer(amount): Sends ETH, forwards 2300 gas, reverts on failure. ❌ Avoid (breaks with gas cost changes).
send(amount): Sends ETH, forwards 2300 gas, returnsfalseon failure. ❌ Avoid (same issues as transfer).
call{value: amount}(""): Sends ETH, forwards all available gas, returns(bool success, bytes memory data). ✅ Recommended.
Best practice (2026):
(bool success, ) = recipient.call{value: amount}("");
require(success, "ETH transfer failed");
Why they ask this: Tests if you're up to date with modern best practices.
---
3. What are function visibility specifiers and when do you use each?
Answer:
public: Callable from anywhere (externally, internally, derived contracts). Generates automatic getter for state variables.
external: Only callable from outside the contract. More gas-efficient for large arrays (uses calldata).
internal: Only callable within the contract or derived contracts.
private: Only callable within the defining contract (not derived contracts).
Pro tip: Use external for functions that are never called internally — saves gas.
---
4. What's the difference between view, pure, and no modifier?
Answer:
- No modifier: Can read AND modify state. Costs gas when called in a transaction.
view: Can READ state but not modify it. Free when called externally (off-chain). Costs gas if called by another function in a transaction.
pure: Cannot read OR modify state. Only uses function parameters and local variables.
Common mistake: Marking a function view when it calls another contract (that might change state) — this will fail to compile in recent Solidity versions.
---
5. How does delegatecall differ from call?
Answer:
call: Executes code in the context of the CALLED contract (uses its storage,msg.senderchanges).
delegatecall: Executes code in the context of the CALLING contract (uses caller's storage,msg.senderpreserved).
Use case: Proxy patterns, upgradeable contracts, libraries.
Security risk: Storage layout must match EXACTLY between proxy and implementation. Misalignment = catastrophic bugs.
// Proxy contract
contract Proxy {
address public implementation;
fallback() external payable {
address impl = implementation;
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
}
---
6. What is the difference between == and = in Solidity?
Answer:
=: Assignment operator (sets a value).
==: Comparison operator (checks equality).
Why they ask: Surprisingly common bug. Solidity's compiler now warns about assignments in conditionals, but interviewers still test this.
---
7. What are events and why are they important?
Answer:
Events are a way to emit logs that are stored in the blockchain's transaction receipt (not in contract storage). They are:
- Cheaper than storage (SSTORE costs 20k gas, LOG costs ~375-1500 gas per event).
- Accessible off-chain via JSON-RPC and indexing services (The Graph, Etherscan).
- Not accessible on-chain (other contracts can't read events).
Use cases:
- State change notifications (Transfer, Approval)
- Off-chain indexing for dApps
- Audit trails
event Transfer(address indexed from, address indexed to, uint256 value);
function transfer(address to, uint256 amount) public {
balances[msg.sender] -= amount;
balances[to] += amount;
emit Transfer(msg.sender, to, amount); // ✅ Always emit after state changes
}
Follow-up: "Why use indexed parameters?"
Answer: Makes events filterable. Max 3 indexed parameters per event.
---
8. What are modifiers and when should you use them?
Answer:
Modifiers are reusable code that runs before or after a function. Common use: access control, validation, reentrancy guards.
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_; // Function body executes here
}
function withdraw() external onlyOwner {
// Only owner can call this
}
Advanced tip: The _; placeholder determines when the function body runs. You can have code after it:
modifier checkInvariant() {
_;
assert(balance >= reserveBalance); // Runs AFTER function
}
---
Security (Questions 9-16)
9. Explain reentrancy and how to prevent it.
Answer:
Reentrancy occurs when a contract calls an external contract, which then calls back into the original contract before the first execution completes.
Classic example (vulnerable):
function withdraw(uint amount) public {
require(balances[msg.sender] >= amount);
(bool success, ) = msg.sender.call{value: amount}(""); // 🚨 External call BEFORE state update
require(success);
balances[msg.sender] -= amount; // 🚨 State updated too late
}
Attack: Attacker's fallback function calls withdraw() again before balances is updated.
Fixes:
function withdraw(uint amount) public {
require(balances[msg.sender] >= amount); // Check
balances[msg.sender] -= amount; // Effect
(bool success, ) = msg.sender.call{value: amount}(""); // Interaction
require(success);
}
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract Safe is ReentrancyGuard {
function withdraw(uint amount) public nonReentrant {
// Protected
}
}
---
10. What is integer overflow/underflow and how does Solidity 0.8+ handle it?
Answer:
Pre-0.8: Integers wrapped around silently.
uint8 x = 255;
x += 1; // Becomes 0 (overflow)
Solidity 0.8+: Built-in overflow/underflow protection. Automatically reverts on overflow.
When to use unchecked: When you're certain overflow is impossible and want to save gas.
function increment(uint x) public pure returns (uint) {
unchecked { return x + 1; } // Saves ~20 gas, but dangerous if x can be type(uint).max
}
---
11. What is a front-running attack?
Answer:
An attacker monitors the mempool, sees your transaction, and submits a similar transaction with higher gas price to execute before yours.
Example: You see a profitable DEX arbitrage opportunity and submit a transaction. MEV bot sees it, copies your transaction with 10x gas price, steals the profit.
Mitigations:
- Commit-reveal schemes (submit hash first, reveal later)
- Private mempools (Flashbots)
- Slippage protection (limit orders)
- Time-locks (delay execution)
---
12. What is tx.origin and why shouldn't you use it for authentication?
Answer:
msg.sender: Immediate caller (can be a contract or EOA).
tx.origin: Original EOA that initiated the transaction chain.
Why it's dangerous:
// 🚨 VULNERABLE
function withdraw() public {
require(tx.origin == owner); // Attacker tricks owner into calling malicious contract
payable(owner).transfer(address(this).balance);
}
Attack: Attacker deploys malicious contract that calls your withdraw(). If owner interacts with attacker's contract, tx.origin is still owner, so check passes.
Fix: Always use msg.sender for authentication.
---
13. What is a flash loan attack?
Answer:
Flash loans allow borrowing unlimited funds within a single transaction (must be repaid before transaction ends).
Attack vector: Manipulate protocol state (e.g., oracle prices, liquidity pools) using borrowed funds, exploit the manipulated state, return funds.
Example: Borrow 100M USDC, dump it in a low-liquidity pool to crash price, liquidate positions in a lending protocol, profit, repay loan.
Defense:
- TWAP (Time-Weighted Average Price) oracles
- Liquidity depth checks
- Rate limiting
- Reentrancy guards
---
14. What are access control vulnerabilities?
Answer:
Missing or improper onlyOwner/role checks on sensitive functions.
Example:
// 🚨 VULNERABLE (anyone can mint)
function mint(address to, uint amount) public {
_mint(to, amount);
}
Fix: Use OpenZeppelin's Ownable or AccessControl.
---
15. What is the difference between assert and require?
Answer (pre-0.8):
require: Refunds remaining gas on failure. For input validation.
assert: Consumes all gas on failure. For invariant checks (should never fail).
Solidity 0.8+: Both refund gas. assert should still only be used for internal errors/invariants.
function withdraw(uint amount) public {
require(amount <= balance, "Insufficient balance"); // User error
balance -= amount;
assert(balance >= 0); // Should never fail (invariant)
}
---
16. What is selfdestruct and why is it dangerous?
Answer:
selfdestruct(address payable recipient) deletes the contract and sends its ETH balance to recipient.
Dangers:
- Forcibly sends ETH to recipient (bypasses payable check, can break invariants)
- Permanently destroys code (but storage remains on-chain)
- Can't be called by external contracts after deletion (breaks composability)
2026 note: EIP-6780 (Cancun upgrade) restricts selfdestruct — it now only sends funds, doesn't delete code unless called in same transaction as creation.
---
Gas Optimization (Questions 17-22)
17. How can you optimize gas in Solidity?
Key techniques:
calldata instead of memory for external function parametersuint128 instead of uint256 where possible)unchecked for safe arithmetic&& stops at first false)// ❌ Expensive (reads from storage 3 times)
function bad() public view returns (uint) {
return storageVar + storageVar + storageVar;
}
// ✅ Cheaper (reads from storage once, uses memory)
function good() public view returns (uint) {
uint cached = storageVar;
return cached + cached + cached;
}
---
18. What is storage packing?
Answer:
EVM stores data in 32-byte slots. Multiple small variables can share a slot.
// 🚨 Uses 3 slots (96 bytes)
contract Unpacked {
uint128 a; // Slot 0
uint256 b; // Slot 1 (can't fit in slot 0)
uint128 c; // Slot 2
}
// ✅ Uses 2 slots (64 bytes)
contract Packed {
uint128 a; // Slot 0 (bytes 0-15)
uint128 c; // Slot 0 (bytes 16-31)
uint256 b; // Slot 1
}
Savings: Each storage slot read (SLOAD) costs 2100 gas (cold) or 100 gas (warm).
---
19. What's cheaper: memory or calldata?
Answer: calldata is cheaper because it's read-only and doesn't require copying data.
Rule: Always use calldata for external function parameters that you don't modify.
// ❌ Copies data to memory (expensive)
function processArray(uint[] memory data) external {
for (uint i = 0; i < data.length; i++) {
// read data[i]
}
}
// ✅ Reads directly from calldata (cheap)
function processArray(uint[] calldata data) external {
for (uint i = 0; i < data.length; i++) {
// read data[i]
}
}
---
20. How does immutable save gas compared to constant?
Answer:
constant: Value must be assigned at compile time. Inlined in bytecode (no storage slot).
immutable: Value can be assigned in constructor. Stored in bytecode (not storage). Can't be changed after deployment.
Both are cheaper than state variables because they don't use SLOAD.
contract Example {
uint public constant FEE = 100; // Compile-time constant
address public immutable OWNER; // Set once in constructor
constructor(address _owner) {
OWNER = _owner; // ✅ Allowed
}
}
---
21. What's the gas cost of deploying a contract?
Answer:
Deployment cost = 21,000 (base transaction) + ~200 gas per byte of bytecode + constructor execution + initial storage writes.
Typical costs (2026):
- Simple ERC-20: ~1M - 1.5M gas (~$30-$50 at 30 gwei)
- Complex DeFi protocol: 3M - 6M gas (~$100-$200)
Optimization: Minimize contract size (use libraries, external contracts for large logic).
---
22. What is the difference between SLOAD and SSTORE?
Answer:
- SLOAD: Read from storage. Costs 2100 gas (cold) / 100 gas (warm after first access).
- SSTORE: Write to storage. Costs 20,000 gas (cold) / 2,900 gas (warm) if changing value, or 5,000 gas if setting to zero (refunds 4,200 gas).
Key insight: Storage writes are ~10x more expensive than reads. Cache frequently used values.
---
EVM & Advanced (Questions 23-26)
23. What is the difference between bytecode and runtime bytecode?
Answer:
- Bytecode: Includes constructor code + runtime code. What you deploy.
- Runtime bytecode: Code that remains on-chain after deployment. What executes when you call functions.
Constructor runs once, then is discarded.
---
24. How does the EVM store data in memory?
Answer:
Memory is byte-addressable, but most operations use 32-byte words. Layout:
- 0x00-0x3f: Scratch space for hashing
- 0x40-0x5f: Free memory pointer (points to next available memory)
- 0x60+: Your data
Why it matters: Efficient memory usage can save gas in complex functions.
---
25. What is a function selector?
Answer:
The first 4 bytes of the keccak256 hash of the function signature.
// Function: transfer(address,uint256)
// Signature: "transfer(address,uint256)"
// Selector: keccak256("transfer(address,uint256)")[:4] = 0xa9059cbb
Used for: Routing external calls to the correct function.
---
26. What is the difference between CREATE and CREATE2?
Answer:
- CREATE: Address = keccak256(sender, nonce). Predictable if you know sender's nonce.
- CREATE2: Address = keccak256(0xff, sender, salt, bytecode_hash). Deterministic based on bytecode and salt.
Use case: CREATE2 allows deploying to the same address on multiple chains (same bytecode + salt = same address).
---
DeFi (Questions 27-30)
27. How does a constant product AMM work?
Answer:
Invariant: x * y = k (reserves of token X × reserves of token Y = constant).
When you swap, the product must remain constant:
x_new * y_new = k
Example: Pool has 100 ETH and 200,000 USDC (k = 20,000,000).
You swap 10 ETH. New reserves: 110 ETH.
110 * y_new = 20,000,000 → y_new ≈ 181,818 USDC
You receive 200,000 - 181,818 = 18,182 USDC (minus fees).
Price impact: Larger trades move the price more.
---
28. What is slippage and how do you protect against it?
Answer:
Slippage = difference between expected price and executed price (due to price movement or liquidity).
Protection:
function swap(uint amountIn, uint minAmountOut) external {
uint amountOut = getAmountOut(amountIn);
require(amountOut >= minAmountOut, "Slippage too high");
// execute swap
}
User sets minAmountOut (e.g., "I want at least 95 USDC for my 1 ETH").
---
29. What is a price oracle and why is it important?
Answer:
Oracle = source of off-chain data (e.g., ETH/USD price) on-chain.
Why important: DeFi protocols need prices for:
- Liquidations (lending protocols)
- Swaps (DEXes)
- Synthetic assets (Synthetix)
Types:
- Chainlink: Decentralized oracle network
- Uniswap TWAP: Time-weighted average price from DEX
- Band Protocol: Alternative decentralized oracle
Security: Never use spot price (manipulatable). Use TWAP or decentralized oracles.
---
30. What is a flash loan and how does it work?
Answer:
Borrow unlimited funds, use them, repay in the same transaction. If you can't repay, the entire transaction reverts.
Workflow:
flashLoan(amount) on lender (Aave, dYdX)amountexecuteOperation() functionamount + feeNo collateral needed because it's atomic.
---
Bonus Behavioral Questions
"Why do you want to work in Web3?"
Good answer: Mention decentralization, ownership, composability, specific problem you want to solve.
Bad answer: "To get rich quick" or vague statements.
"Tell me about a bug you found in your code."
Show humility, learning, and systematic debugging approach.
"How do you stay up to date with Solidity/EVM changes?"
Mention: Solidity blog, OpenZeppelin forums, Immunefi, Twitter, audit reports, CTFs.
---
How to Use This Guide
Practice makes perfect. The more you articulate these concepts, the more confident you'll be in the actual interview.
Next step: Build projects that demonstrate these concepts. Practice on Solingo with challenges designed around real interview questions.
Good luck! 🚀