Web3 / Blockchain Developer — Interview Questions
In short: A blockchain is a shared digital ledger copied across many computers.
- Transactions are grouped into blocks; each block stores the hash of the previous block, forming a chain, so changing an old record would break every later link.
- A consensus mechanism lets the computers agree on the next block without a central authority.
- Result: a tamper-evident, transparent, append-only record that participants don't have to trust a single party to maintain.
In short: Web2 is today's platform-centric internet: users create content but companies own the data, accounts and payment rails.
- Web3 adds native ownership via blockchains: users hold assets and identity in wallets (private keys), applications are built on open smart contracts, and value can move peer to peer without intermediaries.
- Trade-offs: user responsibility for keys, scalability limits, UX friction and regulatory uncertainty.
In short: A hash function maps any input to a fixed-size output (SHA-256 → 256 bits) that is deterministic, one-way, collision-resistant and changes completely with any tiny input change.
- Blockchains use hashes to link blocks (each header contains the previous block's hash), summarise transactions (Merkle root), derive addresses, and in Proof of Work mining (finding a hash below a target).
In short: A public (permissionless) blockchain lets anyone read, transact and participate in consensus (Bitcoin, Ethereum) — maximally decentralised and censorship-resistant, but slower and fully transparent.
- A private/permissioned blockchain restricts participants to known organisations (Hyperledger Fabric, Corda) — faster, private data channels, governance by a consortium, but less decentralised;
- often used for supply chains and inter-bank settlement.
In short: A program stored on a blockchain that runs automatically and deterministically when called by a transaction, enforcing rules without intermediaries — e.g. an escrow releasing funds when conditions are met, a token contract tracking balances, a DEX swapping tokens.
- On Ethereum, contracts are written in Solidity, compiled to EVM bytecode, are immutable once deployed (unless proxies are used), and every execution costs gas.
In short: In PoW, miners spend computing power to find a valid block hash;
- the chain with the most cumulative work is canonical — very secure and battle-tested (Bitcoin) but energy-intensive.
- In PoS, validators lock up (stake) coins to earn the right to propose and attest blocks;
- dishonest behaviour is punished by slashing — far more energy-efficient (Ethereum cut energy use by ~99.95% after The Merge in Sep 2022) and gives economic finality, but is more complex and can concentrate stake.
In short: Gas measures the computational work of an EVM operation; every transaction sets a gas limit and pays gasUsed × gas price.
- EIP-1559 (London, 2021) introduced a protocol-set base fee per block (adjusts with congestion and is burned) plus an optional priority tip to the validator;
- users specify
maxFeePerGasandmaxPriorityFeePerGas. - Failed transactions still consume gas.
- Gas prevents infinite loops and spam and pays for network resources.
In short: Ethereum token standards (interfaces).
- ERC-20 defines fungible tokens with
balanceOf,transfer,approve,allowance,transferFromand Transfer/Approval events — every unit is identical (stablecoins, governance tokens). - ERC-721 defines non-fungible tokens where each
tokenIdis unique with anownerOfandtokenURIfor metadata (NFTs). - ERC-1155 supports many fungible and non-fungible token types in one contract with batch transfers.
In short: Layer 2s execute transactions off the main chain and post compressed data and proofs to layer 1, inheriting its security while increasing throughput and cutting fees.
- Optimistic rollups (Arbitrum, Optimism, Base) assume batches are valid and allow fraud proofs during a challenge period (~7 days for L1 withdrawals).
- ZK rollups (zkSync, Starknet, Scroll, Linea) post validity proofs, giving faster finality without a challenge window but with more complex proving.
- EIP-4844 blobs (2024) made L2 data much cheaper.
In short: Generate a unique deposit address or payment reference per order (HD wallet derivation, or a payment contract with order IDs), subscribe to new blocks/Transfer events via an RPC provider (Nethereum with WebSockets or a provider's webhooks), match incoming transfers to orders (exact token contract, amount, decimals), wait for N confirmations or finality before marking paid (re-org safety), make processing idempotent (by tx hash + log index), persist state in SQL Server, handle under/over-payments and expirations, keep signing keys in an HSM (Azure Key Vault Managed HSM) if you send refunds, and monitor RPC failures with fallbacks.
- Also account for KYC/AML and tax rules.
In short: Likely causes: they signed a malicious approval or permit (gasless signature) giving a drainer contract the right to spend their tokens, signed a phishing transaction disguised as a mint/claim, interacted with a compromised frontend, or had malware.
- Protections: show clear human-readable signing (EIP-712), request exact (not unlimited) approvals, warn on known-malicious addresses, use transaction simulation (wallets like Rabby/Blockaid), domain verification, and encourage revoking old approvals (revoke.cash) and using hardware wallets.
In short: Ask: who are the participants and do they distrust each other?
- Who writes data, and who verifies it?
- Blockchain only guarantees that recorded data hasn't been altered — not that it was true when entered (garbage in, garbage out), so you need trusted inputs (IoT sensors, signed attestations).
- Evaluate a permissioned ledger (Hyperledger Fabric) vs a public chain with hashes/anchoring vs a shared database with signed audit logs;
- consider privacy of commercial data, throughput, cost, governance, integration with ERP, and regulation.
- Often the best design anchors document hashes on-chain and keeps data off-chain.
In short: Solidity is a statically typed, contract-oriented programming language for writing smart contracts that run on the Ethereum Virtual Machine.
- The EVM is the sandboxed, deterministic, stack-based virtual machine that every Ethereum node runs to execute contract bytecode identically, metering each operation in gas.
- Solidity compiles to EVM bytecode plus an ABI used by clients to call contract functions.
In short: public — callable internally and externally (state variables get automatic getters).
external— callable only from outside the contract (or viathis.).internal— this contract and contracts that inherit from it (default for state variables).private— only this contract.- Note:
privatedoesn't hide data — all on-chain storage is publicly readable.
In short: view functions can read state but not modify it; pure functions neither read nor modify state (only use their inputs and local variables).
- Both are free when called off-chain via
eth_call, but cost gas when called from within a transaction. - The compiler enforces these restrictions.
In short: They're data locations.
storageis persistent contract state on the blockchain (expensive to write).memoryis temporary, modifiable data that exists for the duration of a function call.calldatais a read-only, non-persistent area holding the arguments of external calls — the cheapest choice for external function array/string/struct parameters.- Assigning storage to a local
storagevariable creates a reference; tomemorycreates a copy.
In short: msg.sender is the immediate caller of the current function — an EOA or another contract.
tx.originis always the EOA that originally signed the transaction.- Using
tx.originfor authorisation is a vulnerability: a malicious contract the user interacts with can call your contract and pass atx.origin == ownercheck. - Always use
msg.sender.
In short: When a contract makes an external call (e.g. sending Ether) before updating its own state, the recipient's code (fallback/receive or a token hook) can call back into the contract and repeat the action — for example withdrawing again before the balance is reduced (The DAO hack, 2016, ~3.6M ETH).
- Prevent it with the Checks-Effects-Interactions pattern (validate, update state, then call out), OpenZeppelin
ReentrancyGuard(nonReentrant), pull-payment designs, and care with token callbacks (ERC-777/721/1155 hooks).
In short: require(cond, "msg") validates inputs/conditions and reverts with an Error(string).
revert CustomError(args)(orrevert("msg")) aborts explicitly, often after anif.- Custom errors (0.8.4+) are cheaper (4-byte selector + args instead of strings) and carry structured data.
assert(cond)checks internal invariants that should never be false; failure raises a Panic error and indicates a bug.- All revert all state changes in the call.
In short: transfer forwards 2,300 gas and reverts on failure;
sendforwards 2,300 gas and returns false on failure;call{value: x}("")forwards all available gas (configurable) and returns (success, data).- Since gas costs changed (EIP-1884) and smart-contract wallets need more gas,
callwith a checked return value is recommended — combined with reentrancy protection and the pull-payment pattern.
In short: Arithmetic is checked by default: overflow/underflow reverts (Panic 0x11) instead of silently wrapping, making SafeMath unnecessary;
unchecked { }blocks opt out for gas savings where safe.- ABI coder v2 became the default.
- Later 0.8.x versions added custom errors (0.8.4), user-defined value types,
bytes.concat/string.concat, named mapping parameters, user-defined operators and transient storage (0.8.28).
In short: A proxy contract holds the state and forwards calls using delegatecall to an implementation contract, which runs in the proxy's storage context;
- upgrading means pointing the proxy to a new implementation (Transparent, UUPS, Beacon patterns).
- Risks: storage layout collisions if variables are reordered, uninitialised implementations being taken over (use
_disableInitializers), forgetting to protect the upgrade function, a malicious or compromised admin (mitigate with multisig + timelock), and loss of the "code is law" guarantee for users.
In short: Reduce storage writes and reads (cache in memory/stack), pack variables into 32-byte slots, use constant/immutable, calldata for external inputs, custom errors, mappings instead of array searches, avoid unbounded loops, use unchecked where overflow can't happen, emit events instead of storing off-chain-only data, batch operations, transient storage for per-transaction flags, and tune the optimizer.
- Measure with gas reports/snapshots and never trade away safety.
In short: Use OpenZeppelin ERC-20 and SafeERC20 (safeTransferFrom for USDC);
- handle decimals correctly (USDC has 6, your token 18);
- checks-effects-interactions plus
nonReentrant; - caps per wallet and a total cap;
- start/end times;
- pausable by a multisig;
- no reliance on spot prices;
- no unbounded loops (let users claim — pull, not push);
- withdrawal of raised funds only by the treasury multisig;
- events for every purchase;
- comprehensive unit/fuzz tests including edge cases (zero amounts, rounding);
- an external audit;
- and legal/compliance review (securities rules, KYC).
In short: Act immediately: if it has a pause or emergency-withdraw function controlled by a multisig, use it;
- otherwise consider a white-hat rescue (exploit it yourself first to move funds to safety, coordinated with security firms such as SEAL 911), notify users and integrators to stop interacting, revoke approvals guidance, coordinate with exchanges/bridges to freeze stolen funds where possible, then deploy a fixed contract, migrate state from a snapshot, compensate users if possible, and publish a post-mortem.
- Prevention: audits, bug bounties, pausability, rate limits and monitoring.
== and ===?In short: === (strict equality) compares type and value without conversion.
==(loose equality) converts operands to a common type first, which produces surprises ("" == 0is true,null == undefinedis true,[] == falseis true).- Always use
===(except, by some style guides,x == nullto check null or undefined).
var, let and const?In short: var is function-scoped, hoisted and initialised as undefined, can be re-declared, and creates a property on the global object at top level.
letandconstare block-scoped and hoisted but in the Temporal Dead Zone until their declaration (accessing them earlier throws).- Neither can be re-declared in the same scope.
constcan't be reassigned, but the object it references can still be mutated.- Default to
const, useletwhen reassigning, avoidvar.
In short: During the creation phase of an execution context, declarations are registered before code runs.
varvariables are hoisted and initialised toundefined.- Function declarations are hoisted with their body (callable before the line they appear on).
let,constandclassare hoisted but uninitialised (TDZ → ReferenceError if used early).- Function expressions and arrow functions follow the rules of the variable they're assigned to.
In short: string, number, bigint, boolean, undefined, null and symbol.
- They are immutable and compared by value.
- Everything else (objects, arrays, functions, dates) is an object compared by reference.
typeof nullreturns "object" due to a historical bug.
null and undefined?In short: undefined means a variable has been declared but not assigned, a missing property, or a function with no return value — set by the engine.
nullis an intentional "no value" assigned by the programmer.typeof undefinedis "undefined",typeof nullis "object";null == undefinedis true butnull === undefinedis false.??and?.treat both as nullish.
In short: A concise function syntax (x => x * 2).
- Differences: no own
this(lexically inherited — great for callbacks), noargumentsobject, can't be used as constructors withnew, noprototype, and implicit return for single expressions. - Don't use them as object methods or prototype methods that need dynamic
this.
In short: A closure is a function that remembers the variables from the scope where it was created, even after that scope has finished executing.
- Example:
function counter() { let c = 0; return () => ++c; }— each returned function has its own privatec. - Practical uses: data privacy, function factories, memoisation caches, debounce/throttle timers, partial application, event handlers capturing loop values (with
let).
In short: JavaScript executes on a single thread with a call stack.
- Asynchronous operations (timers, network, events) are handled by the browser/Node APIs, which queue callbacks when done.
- When the call stack is empty, the event loop first runs all microtasks (promise reactions,
queueMicrotask), then (in browsers) lets rendering happen, then picks the next macrotask (setTimeout, I/O, UI events). - That's why a resolved promise's
.thenruns before asetTimeout(..., 0)callback.
In short: A Promise represents the eventual result of an asynchronous operation, in state pending, fulfilled or rejected; .then/.catch/.finally register reactions.
Promise.allwaits for all and rejects on the first failure.allSettledwaits for all and returns each outcome.racesettles with the first settled promise (success or failure).anyfulfils with the first success and rejects with AggregateError only if all fail.
this work in JavaScript?In short: It depends on the call site: method call → the object before the dot;
- plain function call →
undefinedin strict mode (global object otherwise); - constructor with
new→ the new instance; call/apply/bind→ explicitly set;- event listeners with regular functions → the element;
- arrow functions →
thisof the enclosing lexical scope. - Losing
thiswhen passing methods as callbacks is fixed withbindor arrow functions.
call, apply and bind?In short: All set this explicitly.
call(thisArg, a, b)invokes immediately with individual arguments.apply(thisArg, [a, b])invokes immediately with an array of arguments.bind(thisArg, a)doesn't invoke — it returns a new function withthis(and optionally leading arguments) permanently bound, useful for callbacks and event handlers.
In short: Objects inherit directly from other objects through the internal [[Prototype]] link.
- When a property isn't found on an object, the engine looks up its prototype chain.
- Constructor functions share methods via
Constructor.prototype; Object.create(proto)creates objects with a chosen prototype;- ES6
class/extendsis syntax over the same mechanism (Dog.prototype.__proto__ === Animal.prototype).
In short: Attaching a single event listener to a common ancestor instead of each child, using event bubbling and event.target (often with closest()) to identify which child was interacted with.
- Benefits: fewer listeners (memory/performance), works for elements added later dynamically, simpler cleanup.
- Note: some events don't bubble (
focus,blur— usefocusin/focusout).
Debounce delays execution until calls stop for a given time (search-as-you-type, auto-save, resize end). Throttle guarantees execution at most once per interval (scroll position, mousemove, rate-limited API calls).
const debounce = (fn, ms) => { let t; return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms); }; };
const throttle = (fn, ms) => { let last = 0; return (...a) => { const now = Date.now(); if (now - last >= ms) { last = now; fn(...a); } }; };In short: Unremoved event listeners and timers (setInterval never cleared), references to detached DOM nodes, accidental globals, closures capturing large objects, ever-growing caches/maps, and subscriptions not unsubscribed in SPA components.
- Find them with Chrome DevTools: Performance monitor (JS heap growing), Memory tab heap snapshots compared over time, "Detached elements" panel, and allocation timelines.
- Fix with cleanup on unmount,
WeakMap/WeakReffor metadata, and bounded caches.