100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Function Modifiers in Solidity

Function modifiers are reusable pieces of code that wrap function bodies to enforce preconditions like access control, using the special _ placeholder to mark where the function runs.

Control Flow & FunctionsIntermediate9 min readJul 10, 2026
Analogies

What a Modifier Is

A function modifier is a reusable block of code that runs before and/or after a function body, most often to enforce a precondition such as 'only the owner may call this' or 'the sale must still be open'. You declare it with the modifier keyword and place a special underscore _; where the wrapped function's body should execute. Applying a modifier to a function is as simple as listing its name in the function header. Modifiers exist to eliminate repeated guard code and to make security constraints declarative and easy to audit.

🏏

Cricket analogy: A modifier is like the umpire's no-ball check that runs before every legal delivery is counted — the _; is the moment the actual ball is bowled, gated by the pre-check.

The Underscore Placeholder and Execution Order

The _; inside a modifier marks where the modified function's body is inlined. Code before _; runs first (typically the checks), then the function body executes, then any code after _; runs. You can even place logic after _; for post-conditions, and a modifier may contain _; only once in a straight-line body. When multiple modifiers are applied to a function, they execute left-to-right in the order listed, nesting like layers of an onion — the first modifier's pre-code runs first and its post-code runs last.

🏏

Cricket analogy: Applying two modifiers is like two successive reviews — the front-foot no-ball check wraps the wide check, and they resolve outermost-first then innermost, just as pre-code runs left-to-right.

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Sale {
    address public owner;
    uint256 public deadline;
    bool private locked;

    constructor(uint256 duration) {
        owner = msg.sender;
        deadline = block.timestamp + duration;
    }

    // Access control modifier with a parameter-free check
    modifier onlyOwner() {
        require(msg.sender == owner, "not owner");
        _; // function body runs here
    }

    // Modifier taking an argument
    modifier before(uint256 time) {
        require(block.timestamp < time, "too late");
        _;
    }

    // Reentrancy guard: code before AND after the placeholder
    modifier nonReentrant() {
        require(!locked, "reentrant call");
        locked = true;
        _;
        locked = false; // post-condition cleanup
    }

    // Multiple modifiers run left-to-right
    function buy() external before(deadline) nonReentrant {
        // purchase logic here
    }

    function setOwner(address next) external onlyOwner {
        owner = next;
    }
}

Modifiers can accept arguments, as in before(deadline). The arguments are evaluated when the function is called. This lets one modifier definition enforce different thresholds across many functions, keeping the guard logic in a single, auditable place.

Best Practices and Pitfalls

Keep modifiers small and focused on checks; complex logic belongs in the function body or an internal function, because deeply nested modifier logic is hard to audit and can obscure control flow. A common guideline is that modifiers should mainly perform require checks and simple state toggles, as with a reentrancy guard. Because a modifier without a reachable _; will prevent the function body from ever running, an accidental early return or a missing _; in a branch is a subtle and dangerous bug. Widely used libraries such as OpenZeppelin's Ownable and ReentrancyGuard provide battle-tested modifiers you should prefer over hand-rolled ones.

🏏

Cricket analogy: A good modifier is a crisp lbw check, not a whole coaching session — overload it and, like an umpire adjudicating strategy mid-over, it muddies the game's flow.

A modifier that can reach a code path with no _; will silently skip the function body, and a modifier that reverts under the wrong condition can lock a function forever. Always ensure _; is reachable on every intended path, and never put untrusted external calls before _; in an access-control modifier.

Under the hood, modifiers are inlined into each function they decorate rather than being separate function calls, so reusing one modifier across ten functions increases deployed bytecode size (each call site gets a copy). When bytecode size or deployment gas is a concern, a common pattern is to have the modifier delegate to a small internal function — for example modifier onlyOwner() { _checkOwner(); _; } — so the heavy logic lives once and only a tiny call is inlined at each site. This is exactly the pattern OpenZeppelin adopted in later versions of Ownable.

🏏

Cricket analogy: Inlining a modifier ten times is like printing the full rulebook on ten scorecards; instead you reference one central rulebook (_checkOwner) and each card just points to it.

  • A modifier is reusable code that wraps a function to enforce preconditions or post-conditions.
  • The _; placeholder marks where the wrapped function body executes; pre-code runs before, post-code after.
  • Multiple modifiers execute left-to-right and nest like an onion, unwinding in reverse order.
  • Modifiers can take arguments, letting one definition enforce different thresholds across functions.
  • Keep modifiers small — mostly require checks and simple toggles like a reentrancy guard.
  • An unreachable _; silently skips the function body; ensure _; is reachable on every path.
  • Modifiers are inlined, increasing bytecode; delegate heavy logic to an internal function, as OpenZeppelin does.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#SolidityStudyNotes#FunctionModifiersInSolidity#Function#Modifiers#Solidity#Modifier#Functions#StudyNotes#SkillVeris