BlockchainSoliditySmart Contracts11 min readUpdated

do-while Loop in Solidity: Slot Scanning and At-Least-Once Patterns

By Mudassir Khan — Agentic AI Consultant & AI Systems Architect, Islamabad, Pakistan

Cover illustration for: do-while Loop in Solidity: Slot Scanning and At-Least-Once Patterns

Section 01 · Definition

What is a do-while loop in Solidity?

A do-while loop runs its body before evaluating the condition. The condition appears at the bottom of the construct, after the closing brace, followed by a semicolon.

Quick answer

In one sentence: A Solidity do-while loop always executes its body at least once, then repeats as long as the condition at the bottom remains true — making it the right choice when one execution is required before the exit test is meaningful.

The syntax places do before the opening brace and the while (condition) clause after the closing brace. The trailing semicolon is required and is a common source of compile errors when switching from a regular while to a do-while.

solidity
// Example 1 — runs at least once, even if condition is false from the start
uint256 i = 0;
do {
    i++; // body always executes here first
} while (i < 5);
// i = 5 after the loop

// Example 2 — find the next free parking spot starting from a hint
bool[10] memory occupied; // 10 parking spots
uint256 hint = 3; // start checking from spot 3
uint256 spot = hint;
do {
    if (!occupied[spot]) {
        break; // found a free spot
    }
    spot++;
} while (spot < 10);
// spot is now the first free spot >= hint

That last point matters for correctness: if arr.length is zero, a while loop would never enter the body, but a do-while executes it once regardless. Accessing arr[i] inside that body on an empty array is an out-of-bounds access. Always guard against empty arrays before entering a do-while loop if the body reads from an array.

Section 02 · Comparison

do-while vs while: the one key difference

Both loops compile to equivalent EVM bytecode for the same logic. The only semantic difference is where the condition is evaluated.

Two-column comparison: while loop checks condition before body, do-while checks condition after body, with the key difference labeled as the at-least-once guarantee.
A while loop may run zero times. A do-while always runs at least once. That single difference determines which one to use.

while: condition checked before the first run

If the condition is false when the loop is first reached, the body never executes. This is the correct choice when zero iterations is a valid outcome, for example iterating over an array that may be empty.

do-while: body runs before the first check

The body always executes at least once regardless of the condition value. Use this when one execution is required before it makes sense to test the exit condition — for example, reading the current slot before deciding whether to advance to the next.

Gas and bytecode are equivalent

The Solidity compiler rewrites both forms to the same JUMPI instruction sequence. There is no gas cost difference between an equivalent while and do-while. The choice is about semantics and readability, not gas.

Section 03 · Canonical pattern

Slot scanning: the classic do-while use case

Finding the next available slot in a packed bitmap or array is the most common on-chain use for do-while. You must read the current position before you can decide whether to advance.

solidity
// 100 seats in a venue: true = taken, false = free
bool[100] public seatTaken;

/// Find the next free seat starting from a caller-supplied hint.
/// The do-while checks the hint seat first, then scans forward.
function nextFreeSeat(uint256 hint) public view returns (uint256 seat) {
    require(hint < 100, "hint out of range");
    seat = hint;
    do {
        if (!seatTaken[seat]) return seat; // found a free seat
        unchecked { ++seat; }
    } while (seat < 100);
    revert("no free seats from hint onward");
}

The do-while is natural here because the hint position itself must be checked before the loop decides to advance. A while loop would require duplicating the check before the loop or restructuring the body, making the code harder to read. With do-while the intent is direct: inspect the current slot, move forward if occupied, stop when a free slot is found or the array is exhausted.

The hint-based slot scan is O(n) worst case

If every slot from hint to the end is occupied, the loop runs (256 - hint) iterations. For a production contract, combine this with an off-chain hint passed by the caller so the on-chain scan is typically O(1) amortized. The MAX_SLOTS constant keeps the worst case bounded even if the caller passes a bad hint.

Section 04 · Full contract

Complete example: EventSeating smart contract

A concert hall booking contract with 100 numbered seats. Attendees suggest a preferred seat number; the do-while loop checks that seat first, then scans forward if it is taken. This is the exact scenario where do-while shines — the hint seat must be inspected before deciding to move on. Deploy on Remix and book seats from different accounts.

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

/// @title EventSeating
/// @notice Concert hall with 100 numbered seats.
///         Attendees pay to book a seat; they suggest a preferred
///         seat number and the do-while loop finds the closest
///         available seat from that hint onward.
contract EventSeating {

    // ── Constants ────────────────────────────────────────────────────
    uint256 public constant TOTAL_SEATS  = 100;
    uint256 public constant TICKET_PRICE = 0.01 ether;

    // ── State ────────────────────────────────────────────────────────
    address public owner;

    struct Seat {
        address holder;     // who booked this seat
        uint64  bookedAt;   // block.timestamp at booking
        bool    taken;
    }

    mapping(uint256 => Seat) public seats;   // seat index => Seat
    uint256 public seatsSold;

    // ── Events ───────────────────────────────────────────────────────
    event SeatBooked(address indexed attendee, uint256 seatNumber, uint256 paid);
    event SeatCancelled(address indexed attendee, uint256 seatNumber);
    event Withdrawn(address indexed owner, uint256 amount);

    // ── Modifiers ────────────────────────────────────────────────────
    modifier onlyOwner() {
        require(msg.sender == owner, "not owner");
        _;
    }

    constructor() {
        owner = msg.sender;
    }

    // ── Write ────────────────────────────────────────────────────────

    /// @notice Book a seat starting from a preferred seat number.
    ///         If the preferred seat is taken, the do-while scans forward
    ///         until it finds the next free one.
    /// @param  preferredSeat  The seat number the attendee wants (0-99).
    /// @return bookedSeat     The actual seat number that was booked.
    function bookSeat(uint256 preferredSeat)
        external payable
        returns (uint256 bookedSeat)
    {
        require(msg.value == TICKET_PRICE, "wrong ticket price");
        require(seatsSold < TOTAL_SEATS,   "event fully booked");
        require(preferredSeat < TOTAL_SEATS, "seat out of range");

        // ── do-while: check the preferred seat first, then scan forward ──
        bookedSeat = preferredSeat;
        do {
            if (!seats[bookedSeat].taken) {
                // Found a free seat — book it immediately.
                seats[bookedSeat] = Seat({
                    holder:   msg.sender,
                    bookedAt: uint64(block.timestamp),
                    taken:    true
                });
                unchecked { ++seatsSold; }
                emit SeatBooked(msg.sender, bookedSeat, msg.value);
                return bookedSeat;
            }
            unchecked { ++bookedSeat; }
        } while (bookedSeat < TOTAL_SEATS);
        // ────────────────────────────────────────────────────────────────

        // All seats from preferredSeat to 99 are taken.
        // Try from seat 0 up to preferredSeat - 1.
        bookedSeat = 0;
        do {
            if (!seats[bookedSeat].taken) {
                seats[bookedSeat] = Seat({
                    holder:   msg.sender,
                    bookedAt: uint64(block.timestamp),
                    taken:    true
                });
                unchecked { ++seatsSold; }
                emit SeatBooked(msg.sender, bookedSeat, msg.value);
                return bookedSeat;
            }
            unchecked { ++bookedSeat; }
        } while (bookedSeat < preferredSeat);

        revert("no free seat found"); // unreachable given seatsSold guard
    }

    /// @notice Cancel your booking and receive a refund.
    function cancelSeat(uint256 _seatNumber) external {
        require(_seatNumber < TOTAL_SEATS, "seat out of range");
        Seat storage s = seats[_seatNumber];
        require(s.taken,            "seat not booked");
        require(s.holder == msg.sender, "not your seat");

        delete seats[_seatNumber];
        unchecked { --seatsSold; }
        emit SeatCancelled(msg.sender, _seatNumber);

        (bool ok,) = msg.sender.call{value: TICKET_PRICE}("");
        require(ok, "refund failed");
    }

    // ── Read ─────────────────────────────────────────────────────────

    /// Check whether a specific seat is free.
    function isFree(uint256 _seatNumber) external view returns (bool) {
        require(_seatNumber < TOTAL_SEATS, "seat out of range");
        return !seats[_seatNumber].taken;
    }

    /// Return how many seats are still available.
    function seatsAvailable() external view returns (uint256) {
        return TOTAL_SEATS - seatsSold;
    }

    /// Return the holder of a seat, or address(0) if free.
    function seatHolder(uint256 _seatNumber) external view returns (address) {
        require(_seatNumber < TOTAL_SEATS, "seat out of range");
        return seats[_seatNumber].holder;
    }

    // ── Admin ────────────────────────────────────────────────────────

    /// Withdraw ticket revenue (owner only).
    function withdraw() external onlyOwner {
        uint256 bal = address(this).balance;
        require(bal > 0, "nothing to withdraw");
        (bool ok,) = owner.call{value: bal}("");
        require(ok, "withdraw failed");
        emit Withdrawn(owner, bal);
    }
}

To test in Remix: deploy the contract, then call bookSeat(5) with 0.01 ETH — it books seat 5. Call it again with the same hint from a different account — seat 5 is taken, so the do-while loop scans to seat 6 and books that. Call seatsAvailable() to confirm the count drops with each booking.

Section 05 · Common mistakes

Four do-while mistakes to avoid

do-while shares all the risks of while loops and adds one unique trap: the guaranteed first execution that runs even when it should not.

Forgetting the trailing semicolon

do { ... } while (condition) requires a semicolon after the closing parenthesis. Omitting it is a compile error. This trips up developers who are used to while loops, where there is no trailing semicolon. The Solidity compiler error message is typically 'Expected semicolon after do-while statement'.

Accessing array elements without a length guard

The do-while body always executes at least once. If the body reads arr[i] and arr is empty, that is an out-of-bounds access that reverts. Always add require(arr.length > 0) or an equivalent guard before a do-while that reads array elements inside the body.

Using do-while when zero iterations is valid

If the task is simply 'iterate over a list that may be empty', a while or for loop is correct. Using do-while forces at least one iteration, which is a semantic error when the list is empty. Reserve do-while for cases where one execution is genuinely required before the condition is meaningful.

Infinite loop from a non-advancing condition variable

Identical to the while loop risk: if the variable checked in the condition is never modified inside the body, the loop runs forever. In a do-while this is especially subtle because the first iteration succeeds before you notice the condition is stuck. Every do-while body must make progress toward a false condition on every code path.

Section 07 · FAQ

Frequently asked questions

Answers to the most common questions about do-while loops in Solidity.

What is a do-while loop in Solidity in simple terms?

A do-while loop runs its body once unconditionally, then checks a condition. If the condition is true, the body runs again. This repeats until the condition becomes false. The key difference from a regular while loop is that the body always runs at least once, even if the condition was false from the very beginning.

When should I use do-while instead of while in Solidity?

Use do-while when one execution of the body is required before the exit condition becomes meaningful. The canonical example is scanning for the next available slot starting from a hint: you must read the hint position before deciding whether to advance. If zero iterations is a valid outcome, use while or for instead.

Does Solidity support do-while loops?

Yes. Solidity has full support for do-while loops with the standard syntax: do { body } while (condition); — note the required semicolon at the end. do-while compiles to the same EVM JUMPI instruction as an equivalent while loop. The Solidity documentation lists it as one of the three supported loop constructs alongside for and while.

Is do-while safe from infinite loops in Solidity?

No more safe than a while loop. If the condition variable is never modified inside the body, the condition stays true forever and the loop exhausts all gas, reverting the transaction. The do-while guarantee of one free execution does not protect against infinite loops — it just means the first iteration runs without a condition check. Every body must advance toward a false condition.

Can I use break and continue inside a do-while loop in Solidity?

Yes. break exits the loop immediately before the condition is checked. continue skips the rest of the body and jumps to the condition check at the bottom of the loop. Both work identically in do-while, while, and for loops. Note that continue in a do-while still evaluates the condition before deciding whether to run the body again.

What is the gas cost of a do-while loop in Solidity?

A do while loop costs roughly the same per iteration as an equivalent while loop because both compile down to a JUMPI condition check at the end of the body. The only difference is one guaranteed body execution before the first condition check, which is usually a few extra gas at most. The body opcodes dominate total cost, not the loop shape.

Can I use do-while for retry logic on chain in Solidity?

You can write retry shaped logic with do while, but on chain retries rarely make sense because every attempt costs gas and the chain state does not change between attempts inside the same transaction. The valid use case is scanning across positions (slot scan, wrap around search) where each attempt examines a different state. For network or off chain retries, retry from the caller, not inside the contract.

Written by Mudassir Khan

Agentic AI consultant and AI systems architect based in Islamabad, Pakistan. CEO of Cube A Cloud. 38+ agentic AI launches delivered for global founders and CTOs.

View agentic AI consulting serviceSee ChainTrust case study

Related service

Agentic AI Consulting

See scope & pricing →

Related case study

ChainTrust Compliance Engine

Read case study →

More on this topic

Need an AI systems architect?

Book a 30-minute architecture call. I will sketch the high-level design for your use case and give you an honest view of the trade-offs.

Book a strategy call →