Three defenses stop most reentrancy bugs: order your code so state changes come before external calls, add a reentrancy lock, or let users pull their own funds instead of pushing payments to them. The strongest designs combine the first with one of the others rather than trusting a single layer.
The problem in one paragraph
When a contract sends ether or calls another contract, control passes to the receiver. The Solidity documentation notes that the receiver can call back into the original contract before the first call finishes. If the original has not yet updated its books, the second call sees stale state.
Defense 1: checks-effects-interactions
Run all checks first. Then update state. Make external calls last. The Solidity documentation puts it this way: interaction with other contracts should be the very last step in any function, and it recommends this even for calls to contracts you know.
function withdraw(uint amount) external {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
(bool ok, ) = msg.sender.call{value: amount}("");
require(ok);
}
Cost: nothing at runtime. Weakness: it depends on every developer, on every change, keeping the order right.
Defense 2: a reentrancy lock
A mutex modifier sets a flag on entry and clears it on exit. A second entry while the flag is set reverts. The Ethereum security guide lists this next to checks-effects-interactions, and OpenZeppelin ships a ready-made guard in its contracts library.
Cost: a little extra gas, and a lock on one contract does not stop reentry through a different contract in a multi-contract system. Use it as a backstop, not the only line.
Defense 3: pull payments
Instead of sending funds inside your logic, record what each user is owed and let them call a separate withdraw function. A failing or hostile receiver then harms only itself. The Solidity documentation also prefers a withdrawal pattern because transfer and send can fail in several ways.
Which to choose
- Any function that sends value: checks-effects-interactions, always.
- Functions with several external calls: add a lock as well.
- Paying many recipients: pull payments.
- Complex multi-contract system: all three, plus review of cross-contract paths.
What none of them replace
Tests, static analysis and review. The Ethereum security guide calls audits useful but not a silver bullet, and says unit tests alone are minimally effective for security.
Quick check
Does a reentrancy lock make checks-effects-interactions unnecessary?
No. The lock covers one contract. Ordering your own state updates first protects you even when the lock is missing or bypassed.
Is reentrancy only about sending ether?
No. The Solidity documentation says it applies to any external call, not only ether transfers.
For the incident that made this famous, see the DAO hack of 2016.
Sources
Last reviewed: 19 October 2026.