Over the years I’ve spent building and auditing smart contracts—from chaotic ICO booms to the steady grind of the current market—I keep running into a familiar set of questions that always surface in Solidity interviews. If you’re serious about leveling up, these aren’t just “gotchas”—they’re real-world challenges you’ll see in production.
(1) Explain the difference between msg.sender, tx.origin, and address(this).
This comes up time and again. I remember back in the 2018 cycle, misuse of tx.origin was a regular culprit in writeups on r/ethdev. msg.sender is your immediate caller—the bread and butter for safe, modular contract design. tx.origin traces all the way to the external wallet that started it all. It’s tempting to use but opens doors to attacks (I’ve seen otherwise-solid projects slip up here). address(this) simply refers to the current contract, which can be handy for self-calls or fallback patterns.
Quick tip: Always think through the call chain—one small oversight can snowball fast.
(2) How does Solidity handle inheritance and multiple inheritance conflicts?
Solidity’s take on inheritance has matured a lot since the early days. It uses a directed acyclic graph and C3 Linearization to settle which method gets called when there’s overlap. If you’ve ever debugged a sprawling codebase with multi-level inheritance, you know “diamond inheritance” isn’t just theory—it causes headaches. Recently, I’ve leaned more toward composition to keep things explicit.
(3) What are view and pure functions?
This is a signpost for how you think about gas and security. view functions read state without changing it, while pure functions don’t even look at the blockchain state. Years ago, people got lazy with these; auditors will absolutely call it out now. I’ve found that proper usage makes both audits and maintenance easier—and saves a bit on fees.
(4) Explain revert(), assert(), and require() statements and their differences.
Error handling is one of those things you only appreciate once you’ve debugged a failed mainnet deploy. require() is my go-to for input validation and user-facing errors. assert()? I reserve it for internal invariants (and honestly, hardly ever use it these days). revert() gives you flexibility to bail out with a message anywhere, but don’t overdo it.
Lesson learned: Clear messages save time, especially when your contract is live and the stakes are real.
(5) How do you handle reentrancy attacks in Solidity?
The “checks-effects-interactions” pattern is still carved into my brain from the Parity days. Always check your logic, update state, and only then interact externally. Most teams I collaborate with now use OpenZeppelin’s ReentrancyGuard, it’s one less attack vector to worry about, and frankly, custom solutions often miss weird edge cases.
If you plan to go deeper, don’t just skim tutorials. Pull up actual hack post-mortems, read the Solidity changelogs, and join a couple of CTFs to pressure test your assumptions. In this space, the best devs are the ones who keep learning—and aren’t afraid to admit what they don’t know.
What’s worked (or tripped you up) in your own experience with these patterns? Have you found any interview questions in this vein you think more people should be talking about?
If anyone wants resource recommendations or a sanity check on their code, give me a shout—always happy to keep the exchange going.