Web3 Interview Questions - Medium
Medium-level Web3 interview questions covering DeFi, advanced Solidity, security, and protocol design.
Q1: Explain DeFi and common DeFi protocols.
Answer:
DeFi Stack
Automated Market Maker (AMM)
Constant Product Formula: $x \times y = k$
Q2: What are Solidity modifiers and function visibility?
Answer:
Modifiers
1contract AccessControl {
2 address public owner;
3 mapping(address => bool) public admins;
4
5 constructor() {
6 owner = msg.sender;
7 }
8
9 // Modifier: reusable check
10 modifier onlyOwner() {
11 require(msg.sender == owner, "Not owner");
12 _; // Continue execution
13 }
14
15 modifier onlyAdmin() {
16 require(admins[msg.sender], "Not admin");
17 _;
18 }
19
20 modifier validAddress(address _addr) {
21 require(_addr != address(0), "Invalid address");
22 _;
23 }
24
25 // Using modifiers
26 function transferOwnership(address newOwner)
27 public
28 onlyOwner
29 validAddress(newOwner)
30 {
31 owner = newOwner;
32 }
33
34 function addAdmin(address admin) public onlyOwner {
35 admins[admin] = true;
36 }
37
38 function restrictedFunction() public onlyAdmin {
39 // Admin-only logic
40 }
41}
Function Types
1contract FunctionTypes {
2 uint256 public value;
3
4 // public: Can be called internally and externally
5 function publicFunction() public returns (uint256) {
6 return value;
7 }
8
9 // external: Only external calls (more gas efficient)
10 function externalFunction() external returns (uint256) {
11 return value;
12 }
13
14 // internal: This contract and derived contracts
15 function internalFunction() internal returns (uint256) {
16 return value * 2;
17 }
18
19 // private: Only this contract
20 function privateFunction() private returns (uint256) {
21 return value * 3;
22 }
23
24 // view: Reads state, doesn't modify
25 function viewFunction() public view returns (uint256) {
26 return value;
27 }
28
29 // pure: Doesn't read or modify state
30 function pureFunction(uint256 a, uint256 b) public pure returns (uint256) {
31 return a + b;
32 }
33
34 // payable: Can receive ETH
35 function payableFunction() public payable {
36 // msg.value contains sent ETH
37 }
38}
Q3: Explain reentrancy attacks and how to prevent them.
Answer:
Vulnerable Contract
1// ❌ VULNERABLE to reentrancy
2contract VulnerableBank {
3 mapping(address => uint256) public balances;
4
5 function deposit() public payable {
6 balances[msg.sender] += msg.value;
7 }
8
9 function withdraw(uint256 amount) public {
10 require(balances[msg.sender] >= amount, "Insufficient balance");
11
12 // DANGER: External call before state update
13 (bool success, ) = msg.sender.call{value: amount}("");
14 require(success, "Transfer failed");
15
16 // State updated AFTER external call
17 balances[msg.sender] -= amount;
18 }
19}
Attack Contract
1contract Attacker {
2 VulnerableBank public bank;
3
4 constructor(address _bank) {
5 bank = VulnerableBank(_bank);
6 }
7
8 function attack() public payable {
9 bank.deposit{value: 1 ether}();
10 bank.withdraw(1 ether);
11 }
12
13 // Fallback function called when receiving ETH
14 receive() external payable {
15 if (address(bank).balance >= 1 ether) {
16 bank.withdraw(1 ether); // Reenter!
17 }
18 }
19}
Attack Flow
Prevention Methods
1// ✅ Method 1: Checks-Effects-Interactions Pattern
2contract SafeBank1 {
3 mapping(address => uint256) public balances;
4
5 function withdraw(uint256 amount) public {
6 require(balances[msg.sender] >= amount, "Insufficient balance");
7
8 // Update state BEFORE external call
9 balances[msg.sender] -= amount;
10
11 // External call last
12 (bool success, ) = msg.sender.call{value: amount}("");
13 require(success, "Transfer failed");
14 }
15}
16
17// ✅ Method 2: ReentrancyGuard
18import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
19
20contract SafeBank2 is ReentrancyGuard {
21 mapping(address => uint256) public balances;
22
23 function withdraw(uint256 amount) public nonReentrant {
24 require(balances[msg.sender] >= amount, "Insufficient balance");
25
26 balances[msg.sender] -= amount;
27 (bool success, ) = msg.sender.call{value: amount}("");
28 require(success, "Transfer failed");
29 }
30}
31
32// ✅ Method 3: Pull over Push
33contract SafeBank3 {
34 mapping(address => uint256) public balances;
35 mapping(address => uint256) public pendingWithdrawals;
36
37 function initiateWithdrawal(uint256 amount) public {
38 require(balances[msg.sender] >= amount, "Insufficient balance");
39 balances[msg.sender] -= amount;
40 pendingWithdrawals[msg.sender] += amount;
41 }
42
43 function completeWithdrawal() public {
44 uint256 amount = pendingWithdrawals[msg.sender];
45 require(amount > 0, "No pending withdrawal");
46
47 pendingWithdrawals[msg.sender] = 0;
48 (bool success, ) = msg.sender.call{value: amount}("");
49 require(success, "Transfer failed");
50 }
51}
Q4: What are proxy patterns and upgradeable contracts?
Answer:
Proxy Architecture
Transparent Proxy Pattern
1// Proxy contract (never changes)
2contract TransparentProxy {
3 address public implementation;
4 address public admin;
5
6 constructor(address _implementation) {
7 implementation = _implementation;
8 admin = msg.sender;
9 }
10
11 modifier onlyAdmin() {
12 require(msg.sender == admin, "Not admin");
13 _;
14 }
15
16 function upgradeTo(address newImplementation) external onlyAdmin {
17 implementation = newImplementation;
18 }
19
20 fallback() external payable {
21 address impl = implementation;
22
23 assembly {
24 // Copy calldata
25 calldatacopy(0, 0, calldatasize())
26
27 // Delegatecall to implementation
28 let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
29
30 // Copy return data
31 returndatacopy(0, 0, returndatasize())
32
33 switch result
34 case 0 { revert(0, returndatasize()) }
35 default { return(0, returndatasize()) }
36 }
37 }
38}
39
40// Implementation V1
41contract ImplementationV1 {
42 uint256 public value;
43
44 function setValue(uint256 _value) public {
45 value = _value;
46 }
47
48 function getValue() public view returns (uint256) {
49 return value;
50 }
51}
52
53// Implementation V2 (upgraded)
54contract ImplementationV2 {
55 uint256 public value;
56
57 function setValue(uint256 _value) public {
58 value = _value;
59 }
60
61 function getValue() public view returns (uint256) {
62 return value;
63 }
64
65 // New function
66 function increment() public {
67 value++;
68 }
69}
Storage Collision
Solution: Use storage gaps or structured storage
1contract ImplementationV1 {
2 // Reserve slots for proxy
3 bytes32 private constant IMPLEMENTATION_SLOT =
4 bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1);
5 bytes32 private constant ADMIN_SLOT =
6 bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1);
7
8 // Implementation storage starts here
9 uint256 public value;
10}
Q5: Explain oracles and Chainlink.
Answer:
Oracle Architecture
Chainlink Price Feed
1import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
2
3contract PriceConsumer {
4 AggregatorV3Interface internal priceFeed;
5
6 constructor() {
7 // ETH/USD price feed on Ethereum mainnet
8 priceFeed = AggregatorV3Interface(
9 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419
10 );
11 }
12
13 function getLatestPrice() public view returns (int) {
14 (
15 uint80 roundId,
16 int price,
17 uint startedAt,
18 uint updatedAt,
19 uint80 answeredInRound
20 ) = priceFeed.latestRoundData();
21
22 return price; // Returns price with 8 decimals
23 }
24
25 function getDecimals() public view returns (uint8) {
26 return priceFeed.decimals();
27 }
28}
Custom Oracle Request
1import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol";
2
3contract APIConsumer is ChainlinkClient {
4 using Chainlink for Chainlink.Request;
5
6 uint256 public volume;
7 address private oracle;
8 bytes32 private jobId;
9 uint256 private fee;
10
11 constructor() {
12 setPublicChainlinkToken();
13 oracle = 0x...; // Oracle address
14 jobId = "..."; // Job ID
15 fee = 0.1 * 10 ** 18; // 0.1 LINK
16 }
17
18 function requestVolumeData() public returns (bytes32 requestId) {
19 Chainlink.Request memory request = buildChainlinkRequest(
20 jobId,
21 address(this),
22 this.fulfill.selector
23 );
24
25 // Set the URL to perform the GET request on
26 request.add("get", "https://api.example.com/volume");
27 request.add("path", "data.volume");
28
29 // Sends the request
30 return sendChainlinkRequestTo(oracle, request, fee);
31 }
32
33 function fulfill(bytes32 _requestId, uint256 _volume) public recordChainlinkFulfillment(_requestId) {
34 volume = _volume;
35 }
36}
Q6: What are Layer 2 solutions?
Answer:
Rollup Architecture
Optimistic vs ZK Rollups
Bridge Contract
1// Simplified L1 → L2 bridge
2contract L1Bridge {
3 address public l2Bridge;
4
5 event DepositInitiated(
6 address indexed from,
7 address indexed to,
8 uint256 amount
9 );
10
11 function depositETH(address to) public payable {
12 require(msg.value > 0, "Must send ETH");
13
14 emit DepositInitiated(msg.sender, to, msg.value);
15
16 // L2 sequencer watches this event
17 // and mints equivalent on L2
18 }
19
20 function finalizeWithdrawal(
21 address to,
22 uint256 amount,
23 bytes memory proof
24 ) public {
25 // Verify proof from L2
26 require(verifyProof(proof), "Invalid proof");
27
28 // Transfer ETH
29 (bool success, ) = to.call{value: amount}("");
30 require(success, "Transfer failed");
31 }
32
33 function verifyProof(bytes memory proof) internal view returns (bool) {
34 // Verify Merkle proof or fraud proof
35 // Implementation depends on L2 type
36 return true;
37 }
38}
Q7: Explain EIP-1559 and gas optimization.
Answer:
Gas Calculation (EIP-1559)
Formula: $$\text{Total Fee} = \text{Gas Used} \times (\text{Base Fee} + \text{Priority Fee})$$
Gas Optimization Techniques
1contract GasOptimization {
2 // ❌ Expensive: Storage writes
3 uint256 public counter;
4
5 function incrementBad() public {
6 counter = counter + 1; // ~5,000 gas
7 }
8
9 // ✅ Better: Use += operator
10 function incrementGood() public {
11 counter += 1; // ~5,000 gas (same, but clearer)
12 }
13
14 // ✅ Best: Cache in memory
15 function incrementBest() public {
16 uint256 temp = counter;
17 temp += 1;
18 counter = temp;
19 }
20
21 // ❌ Expensive: Dynamic array in storage
22 uint256[] public numbers;
23
24 function sumBad() public view returns (uint256) {
25 uint256 total = 0;
26 for (uint256 i = 0; i < numbers.length; i++) {
27 total += numbers[i]; // Each read: ~2,100 gas
28 }
29 return total;
30 }
31
32 // ✅ Better: Cache length
33 function sumGood() public view returns (uint256) {
34 uint256 total = 0;
35 uint256 length = numbers.length; // Cache length
36 for (uint256 i = 0; i < length; i++) {
37 total += numbers[i];
38 }
39 return total;
40 }
41
42 // ✅ Use calldata for external functions
43 function processBad(uint256[] memory data) public pure returns (uint256) {
44 return data.length;
45 }
46
47 function processGood(uint256[] calldata data) external pure returns (uint256) {
48 return data.length; // Cheaper: no copy to memory
49 }
50
51 // ✅ Pack variables
52 struct Inefficient {
53 uint8 a; // Slot 0
54 uint256 b; // Slot 1
55 uint8 c; // Slot 2
56 }
57
58 struct Efficient {
59 uint8 a; // Slot 0
60 uint8 c; // Slot 0 (packed)
61 uint256 b; // Slot 1
62 }
63
64 // ✅ Use events instead of storage
65 event DataStored(uint256 indexed id, string data);
66
67 function storeInEvent(uint256 id, string memory data) public {
68 emit DataStored(id, data); // Much cheaper than storage
69 }
70
71 // ✅ Short-circuit conditions
72 function checkBad(uint256 a, uint256 b) public pure returns (bool) {
73 return expensiveCheck(a) && cheapCheck(b);
74 }
75
76 function checkGood(uint256 a, uint256 b) public pure returns (bool) {
77 return cheapCheck(b) && expensiveCheck(a); // Cheap first
78 }
79
80 function cheapCheck(uint256 x) internal pure returns (bool) {
81 return x > 0;
82 }
83
84 function expensiveCheck(uint256 x) internal pure returns (bool) {
85 // Expensive computation
86 return x % 2 == 0;
87 }
88}
Q8: What are signatures and how to verify them on-chain?
Answer:
Signature Process
Off-chain Signing
1const { ethers } = require('ethers');
2
3// Sign message off-chain
4async function signMessage(wallet, message) {
5 // Create message hash
6 const messageHash = ethers.utils.id(message);
7
8 // Sign
9 const signature = await wallet.signMessage(ethers.utils.arrayify(messageHash));
10
11 // Split signature
12 const sig = ethers.utils.splitSignature(signature);
13
14 return {
15 message,
16 messageHash,
17 signature,
18 r: sig.r,
19 s: sig.s,
20 v: sig.v
21 };
22}
23
24// Example
25const wallet = new ethers.Wallet('0x...');
26const signed = await signMessage(wallet, "Hello World");
On-chain Verification
1contract SignatureVerifier {
2 function verifySignature(
3 address signer,
4 string memory message,
5 bytes memory signature
6 ) public pure returns (bool) {
7 bytes32 messageHash = keccak256(abi.encodePacked(message));
8 bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);
9
10 address recoveredSigner = recoverSigner(ethSignedMessageHash, signature);
11 return recoveredSigner == signer;
12 }
13
14 function getEthSignedMessageHash(bytes32 messageHash)
15 internal
16 pure
17 returns (bytes32)
18 {
19 return keccak256(
20 abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash)
21 );
22 }
23
24 function recoverSigner(bytes32 ethSignedMessageHash, bytes memory signature)
25 internal
26 pure
27 returns (address)
28 {
29 (bytes32 r, bytes32 s, uint8 v) = splitSignature(signature);
30 return ecrecover(ethSignedMessageHash, v, r, s);
31 }
32
33 function splitSignature(bytes memory sig)
34 internal
35 pure
36 returns (bytes32 r, bytes32 s, uint8 v)
37 {
38 require(sig.length == 65, "Invalid signature length");
39
40 assembly {
41 r := mload(add(sig, 32))
42 s := mload(add(sig, 64))
43 v := byte(0, mload(add(sig, 96)))
44 }
45 }
46}
47
48// Usage: Meta-transactions
49contract MetaTransaction {
50 mapping(address => uint256) public nonces;
51
52 function executeMetaTransaction(
53 address user,
54 bytes memory functionSignature,
55 bytes32 r,
56 bytes32 s,
57 uint8 v
58 ) public returns (bytes memory) {
59 bytes32 messageHash = keccak256(
60 abi.encodePacked(user, functionSignature, nonces[user])
61 );
62
63 address signer = ecrecover(messageHash, v, r, s);
64 require(signer == user, "Invalid signature");
65
66 nonces[user]++;
67
68 (bool success, bytes memory returnData) = address(this).call(
69 functionSignature
70 );
71 require(success, "Function call failed");
72
73 return returnData;
74 }
75}
Q9: Explain flash loans and how they work.
Answer:
Flash Loan Flow
Flash Loan Implementation
1// Aave flash loan interface
2interface IFlashLoanReceiver {
3 function executeOperation(
4 address[] calldata assets,
5 uint256[] calldata amounts,
6 uint256[] calldata premiums,
7 address initiator,
8 bytes calldata params
9 ) external returns (bool);
10}
11
12contract FlashLoanArbitrage is IFlashLoanReceiver {
13 ILendingPool public lendingPool;
14
15 constructor(address _lendingPool) {
16 lendingPool = ILendingPool(_lendingPool);
17 }
18
19 function executeFlashLoan(address asset, uint256 amount) public {
20 address[] memory assets = new address[](1);
21 assets[0] = asset;
22
23 uint256[] memory amounts = new uint256[](1);
24 amounts[0] = amount;
25
26 uint256[] memory modes = new uint256[](1);
27 modes[0] = 0; // 0 = no debt, flash loan
28
29 lendingPool.flashLoan(
30 address(this),
31 assets,
32 amounts,
33 modes,
34 address(this),
35 "",
36 0
37 );
38 }
39
40 function executeOperation(
41 address[] calldata assets,
42 uint256[] calldata amounts,
43 uint256[] calldata premiums,
44 address initiator,
45 bytes calldata params
46 ) external override returns (bool) {
47 // This function is called by the lending pool
48 // You have the borrowed funds here
49
50 // 1. Perform arbitrage or other operations
51 performArbitrage(assets[0], amounts[0]);
52
53 // 2. Approve the lending pool to pull the funds back
54 uint256 amountOwed = amounts[0] + premiums[0];
55 IERC20(assets[0]).approve(address(lendingPool), amountOwed);
56
57 // 3. Return true to indicate success
58 return true;
59 }
60
61 function performArbitrage(address asset, uint256 amount) internal {
62 // Example: Buy low on DEX1, sell high on DEX2
63
64 // Buy on Uniswap
65 address[] memory path = new address[](2);
66 path[0] = asset;
67 path[1] = address(USDC);
68
69 IUniswapV2Router(uniswapRouter).swapExactTokensForTokens(
70 amount,
71 0,
72 path,
73 address(this),
74 block.timestamp
75 );
76
77 // Sell on Sushiswap (if price is higher)
78 path[0] = address(USDC);
79 path[1] = asset;
80
81 IUniswapV2Router(sushiswapRouter).swapExactTokensForTokens(
82 USDC.balanceOf(address(this)),
83 amount, // Must get back at least what we borrowed + fee
84 path,
85 address(this),
86 block.timestamp
87 );
88 }
89}
Use Cases:
- Arbitrage between DEXes
- Collateral swap
- Self-liquidation
- Debt refinancing
Q10: What are common smart contract vulnerabilities?
Answer:
Integer Overflow (Pre-Solidity 0.8)
1// ❌ Vulnerable (Solidity < 0.8)
2contract Vulnerable {
3 uint8 public count = 255;
4
5 function increment() public {
6 count++; // Overflows to 0
7 }
8}
9
10// ✅ Fixed: Use SafeMath or Solidity 0.8+
11import "@openzeppelin/contracts/utils/math/SafeMath.sol";
12
13contract Safe {
14 using SafeMath for uint8;
15 uint8 public count = 255;
16
17 function increment() public {
18 count = count.add(1); // Reverts on overflow
19 }
20}
21
22// ✅ Solidity 0.8+ has built-in overflow checks
23contract SafeModern {
24 uint8 public count = 255;
25
26 function increment() public {
27 count++; // Automatically reverts on overflow
28 }
29}
Access Control Vulnerability
1// ❌ Vulnerable: Missing access control
2contract VulnerableWallet {
3 function withdraw(uint256 amount) public {
4 // Anyone can call this!
5 payable(msg.sender).transfer(amount);
6 }
7}
8
9// ✅ Fixed: Proper access control
10contract SafeWallet {
11 address public owner;
12
13 constructor() {
14 owner = msg.sender;
15 }
16
17 modifier onlyOwner() {
18 require(msg.sender == owner, "Not owner");
19 _;
20 }
21
22 function withdraw(uint256 amount) public onlyOwner {
23 payable(msg.sender).transfer(amount);
24 }
25}
Front-Running
1// ❌ Vulnerable to front-running
2contract VulnerableAuction {
3 uint256 public highestBid;
4 address public highestBidder;
5
6 function bid() public payable {
7 require(msg.value > highestBid, "Bid too low");
8
9 // Refund previous bidder
10 payable(highestBidder).transfer(highestBid);
11
12 highestBid = msg.value;
13 highestBidder = msg.sender;
14 }
15}
16
17// ✅ Better: Commit-reveal scheme
18contract SafeAuction {
19 mapping(address => bytes32) public commitments;
20 mapping(address => uint256) public bids;
21
22 // Phase 1: Commit
23 function commit(bytes32 commitment) public {
24 commitments[msg.sender] = commitment;
25 }
26
27 // Phase 2: Reveal
28 function reveal(uint256 bid, bytes32 secret) public payable {
29 require(
30 keccak256(abi.encodePacked(bid, secret)) == commitments[msg.sender],
31 "Invalid reveal"
32 );
33 require(msg.value == bid, "Incorrect payment");
34
35 bids[msg.sender] = bid;
36 }
37}
Denial of Service
1// ❌ Vulnerable: Unbounded loop
2contract VulnerableDistributor {
3 address[] public recipients;
4
5 function distribute() public {
6 for (uint256 i = 0; i < recipients.length; i++) {
7 payable(recipients[i]).transfer(1 ether);
8 // If one transfer fails, entire function fails
9 // If array is too large, runs out of gas
10 }
11 }
12}
13
14// ✅ Fixed: Pull over push
15contract SafeDistributor {
16 mapping(address => uint256) public balances;
17
18 function allocate(address[] memory recipients) public {
19 for (uint256 i = 0; i < recipients.length; i++) {
20 balances[recipients[i]] += 1 ether;
21 }
22 }
23
24 function withdraw() public {
25 uint256 amount = balances[msg.sender];
26 require(amount > 0, "No balance");
27
28 balances[msg.sender] = 0;
29 payable(msg.sender).transfer(amount);
30 }
31}
Summary
Medium Web3 topics:
- DeFi: Lending, DEX, AMM, yield farming
- Solidity Advanced: Modifiers, visibility, function types
- Reentrancy: Attack vectors and prevention
- Proxy Patterns: Upgradeable contracts, delegatecall
- Oracles: Chainlink, price feeds, external data
- Layer 2: Rollups, sidechains, bridges
- EIP-1559: Gas optimization techniques
- Signatures: ECDSA, on-chain verification, meta-transactions
- Flash Loans: Uncollateralized borrowing, arbitrage
- Security: Common vulnerabilities and fixes
These concepts are essential for building secure DeFi protocols.
Related Snippets
- Bitcoin (Nakamoto) Consensus Interview Questions
Bitcoin consensus algorithm interview questions covering Proof-of-Work (PoW) and … - Byzantine Fault Tolerance (BFT) Consensus Interview Questions
Byzantine Fault Tolerance (BFT) consensus algorithm interview questions covering … - Cardano Interview Questions - Easy
Easy-level Cardano interview questions covering blockchain basics, Plutus, and … - Cardano Interview Questions - Hard
Hard-level Cardano interview questions covering advanced optimization and formal … - Cardano Interview Questions - Medium
Medium-level Cardano interview questions covering advanced Plutus development … - Consensus Algorithms Comparison Interview Questions
Consensus algorithm comparison and general implementation interview questions. … - Cosmos Chain Operations Interview Questions - Easy
Easy-level Cosmos chain operation interview questions covering chain operations, … - Cosmos Chain Operations Interview Questions - Hard
Hard-level Cosmos chain operation questions covering advanced algorithms, … - Cosmos Chain Operations Interview Questions - Medium
Medium-level Cosmos chain operation questions covering advanced chain … - Cosmos SDK Interview Questions - Easy
Easy-level Cosmos SDK interview questions covering chain code, SDK basics, and … - Cosmos SDK Interview Questions - Hard
Hard-level Cosmos SDK interview questions covering advanced SDK internals, … - Cosmos SDK Interview Questions - Medium
Medium-level Cosmos SDK interview questions covering advanced module … - Ethereum Proof-of-Stake Consensus Interview Questions
Ethereum Proof-of-Stake consensus algorithm interview questions covering Casper … - Ouroboros (Cardano) Consensus Interview Questions
Ouroboros consensus algorithm interview questions covering Cardano's … - Paxos Consensus Interview Questions
Paxos consensus algorithm interview questions covering the classic distributed … - Polkadot (NPoS) Consensus Interview Questions
Polkadot consensus algorithm interview questions covering Nominated … - Polkadot Interview Questions - Easy
Easy-level Polkadot interview questions covering blockchain basics, Substrate, … - Polkadot Interview Questions - Hard
Hard-level Polkadot interview questions covering advanced optimization and … - Polkadot Interview Questions - Medium
Medium-level Polkadot interview questions covering advanced Substrate … - Solana Interview Questions - Easy
Easy-level Solana interview questions covering blockchain basics, programs, and … - Solana Interview Questions - Hard
Hard-level Solana interview questions covering advanced optimization, security, … - Solana Interview Questions - Medium
Medium-level Solana interview questions covering advanced program development, … - Solana Proof of History Consensus Interview Questions
Solana consensus algorithm interview questions covering Proof of History (PoH) … - Tendermint Consensus Interview Questions
Tendermint consensus algorithm interview questions covering the Byzantine Fault … - Web3 Interview Questions - Easy
Easy-level Web3 interview questions covering blockchain fundamentals, Ethereum, … - Web3 Interview Questions - Hard
Hard-level Web3 interview questions covering MEV, zero-knowledge proofs, …