Solia saysCustom errors compile to a 4-byte selector plus ABI-encoded args — far cheaper than require(cond, "string message"), which embeds the full string in bytecode on revert.
Pre-0.8.4 the only way to revert with context was require(cond, "long message"). The string is embedded in the contract bytecode and re-encoded into revert data each call, costing real gas. Custom errors compile to a 4-byte selector (like a function call) plus ABI-encoded args, dropping the bytes-on-revert from dozens to a fixed handful.
They also carry structured data: revert InsufficientBalance(uint256 needed, uint256 actual) can be decoded by the frontend to display rich error messages. The demo compares the bytes-on-revert of a require-string vs the equivalent custom error.
Power-ups you unlock
Custom errors compile to a 4-byte selector + encoded args
Require-string revert embeds the full string in bytecode
Bytes saved on every revert path
Errors carry structured data for frontends to decode
Available since Solidity 0.8.4
The Reentrancy Reaper attacks — common mistakes
Mixing custom errors with require-string throughout the codebase
Putting too much data in error args — they are still on-chain bytes
Forgetting custom errors need to be declared (interface or contract)
Assuming custom errors save gas in the success path (they only matter on revert)
Boss battleCompare the calldata-on-revert bytes for require("...long string...") vs revert CustomError(needed, actual).