Reader Stacks

The $loop Variable in Blade's @foreach Directive

Blade automatically injects a $loop variable inside every @foreach with index, first/last flags, and iteration count built in — no manual counter variable needed.

The $loop Variable in Blade's @foreach Directive

Blade automatically injects a special $loop variable inside every @foreach loop, giving access to the current index, first/last flags, and iteration count without needing to manually declare and increment a counter variable yourself.

The basic properties

@foreach ($products as $product)
    Index: {{ $loop->index }} (0-based)
    Iteration: {{ $loop->iteration }} (1-based)
    {{ $product->name }}
@endforeach

Checking for the first or last iteration

@foreach ($products as $product)
    <div class="{{ $loop->first ? 'first-item' : '' }} {{ $loop->last ? 'last-item' : '' }}">
        {{ $product->name }}
    </div>
@endforeach

$loop->first and $loop->last are genuinely useful for conditional styling (removing a border on the last item in a list, adding special styling to the first) without needing to manually compare the current index against the collection's count.

Getting the total item count

@foreach ($products as $product)
    Item {{ $loop->iteration }} of {{ $loop->count }}
@endforeach

Checking for even/odd rows, for alternating table styling

@foreach ($products as $product)
    <tr class="{{ $loop->even ? 'bg-gray-50' : '' }}">
        <td>{{ $product->name }}</td>
    </tr>
@endforeach

Accessing the parent loop from a nested @foreach

@foreach ($categories as $category)
    {{ $category->name }}
    @foreach ($category->products as $product)
        Category {{ $loop->parent->iteration }}, Product {{ $loop->iteration }}: {{ $product->name }}
    @endforeach
@endforeach

$loop->parent is what makes the outer loop's own $loop instance available from inside a nested loop, where $loop would otherwise only refer to the inner loop — necessary any time nested loop logic needs to reference the outer iteration's position.

Checking remaining iterations

@foreach ($items as $item)
    {{ $item->name }}
    @if ($loop->remaining > 0)
        (more items follow)
    @endif
@endforeach

Why $loop is Blade-specific, not a PHP feature

$loop doesn't exist in plain PHP foreach loops at all — it's a Blade compilation feature, automatically injected only when using the @foreach directive specifically, not when using a raw foreach written directly in <?php ?> tags inside a Blade file.

$loop also tells you depth inside nested loops

When a component or template renders recursive-looking nested structures, $loop->depth tells you how deeply nested the current Blade loop is. That can be useful for indentation or data attributes without manually passing a level counter through every partial. Use it for presentation state, not as a substitute for understanding the underlying tree structure.

empty collections have a dedicated Blade pattern

If the view needs an empty state, @forelse is usually cleaner than wrapping @foreach in a separate count check:

@forelse ($products as $product)
    <div>
        {{ $loop->iteration }}. {{ $product->name }}
    </div>
@empty
    <p>No products found.</p>
@endforelse

The $loop variable is available in the iteration branch exactly as it is in @foreach.

Do not use $loop to trigger database work

A tempting pattern is "on the first iteration, load something extra" or calling a relationship for every row while using $loop to format the result. The loop metadata itself is cheap; the hidden problem is database access from the view. Eager-load relationships in the controller/query layer before rendering so a neat Blade loop does not become an N+1 query source.

$loop indexes describe rendered iteration order, not model identity

$loop->index and iteration reset every time the template loops. They are appropriate for display numbering and alternating styles, but not for persistent DOM IDs, form field identity, or business keys. Use the model's stable identifier for those purposes; pagination, sorting, or filtering can change a loop position without changing the underlying record.