skip to content

Conditional & Loop Directives

Blade's @if, @foreach with its $loop variable, @forelse and the @include family replace raw PHP in templates, and Blade::if adds your own. Interviewers ask which directive fits which logic.

on this pageshow

explore

questions

5

Inside a Blade @foreach, what does $loop provide, and how would you use it to mark a pricing table's first, last and alternating rows?

level: juniorimportance: must knowfreq 62%

answer

  1. an object Blade injects per loop
  2. index from 0, iteration from 1
  3. first, last, odd, even, remaining
  4. depth and parent for nested loops
  5. count is null for a LazyCollection

basics

~10 s

Blade sets a $loop object on every @foreach iteration with index (from 0), iteration (from 1), count, remaining, first, last, odd, even, depth and parent, so rows can be styled without a hand-kept counter.

solid answer

~40 s

When Blade compiles `@foreach`, it pushes a loop record onto a stack before the loop and refreshes a `$loop` object on each pass. `$loop->index` counts from 0 and `$loop->iteration` from 1; `$loop->count` and `$loop->remaining` give the size and what is left; `$loop->first` and `$loop->last` are booleans; `$loop->odd` and `$loop->even` follow the 1-based iteration, so the first row is odd; `$loop->depth` and `$loop->parent` expose nesting. For a pricing table, `@if ($loop->first)` can tag the entry tier, `$loop->last` the enterprise tier, and `$loop->even` a striped row. One trap: for a `LazyCollection` or a generator Blade does not count the items, so `count`, `remaining` and `last` are `null`.

code

html · 18 lines
html
<table>
@foreach ($plans as $plan)
    <tr class="{{ $loop->even ? 'bg-muted' : '' }}">
        <td>
            {{ $plan->name }}
            @if ($loop->first) <span class="badge">Start here</span> @endif
        </td>
        <td>{{ $plan->price_label }}</td>
        <td>
            @if ($loop->last)
                <a href="{{ route('sales.contact') }}">Contact sales</a>
            @else
                <a href="{{ route('checkout', $plan) }}">Choose</a>
            @endif
        </td>
    </tr>
@endforeach
</table>

go deeper

for a junior

Recall the main properties - index from 0, iteration from 1, first, last, odd, even - and use them instead of a manual counter.

for a middle

Explain how Blade builds $loop from a stack, how parent and depth work in nested loops, and why parity follows the 1-based iteration.

for a senior

Anticipate where the helpers go missing - lazy collections and generators leave count and last null - and keep filtering and loading out of the loop body.

for a principal

Keep templates thin by convention: controllers prepare ordered, filtered lists, and loops only present them with $loop for layout cues.

## What $loop is **Blade** is Laravel's template engine; its `@foreach` directive compiles to a PHP `foreach` with extra bookkeeping. Before the loop starts, Blade calls the view factory's `addLoop()` with the data being iterated, which pushes a record onto a **loop stack**. At the top of every iteration it calls `incrementLoopIndices()` and assigns the current record, cast to an object, to a variable named `$loop`. When the loop ends, the record is popped and `$loop` points back at the enclosing loop, if any. The same happens for `@forelse`. A plain `@for` or `@while` gets **no** `$loop`, because Blade has no collection to describe. ## The properties | Property | Meaning | First row of a 3-item list | |---|---|---| | `index` | position, starting at 0 | `0` | | `iteration` | position, starting at 1 | `1` | | `count` | total number of items | `3` | | `remaining` | items left after this one | `2` | | `first` | is this the first iteration | `true` | | `last` | is this the last iteration | `false` | | `odd` / `even` | parity of `iteration` | `true` / `false` | | `depth` | nesting level, outermost is 1 | `1` | | `parent` | the enclosing loop's `$loop`, or `null` | `null` | Parity follows the **1-based** iteration: the first row is odd. Mixing that up with the 0-based `index` is the usual off-by-one in striped tables. ## Using it on a pricing table A plans page iterates tiers - Free, Pro, Team, Enterprise - and wants the entry tier labelled, the last tier's card to say "Contact sales", and alternating row shading in a comparison table: - `@if ($loop->first) <span class="badge">Start here</span> @endif` - `@if ($loop->last) Contact sales @else Choose plan @endif` - `<tr class="{{ $loop->even ? 'bg-muted' : '' }}">` for zebra striping. - `{{ $loop->iteration }} of {{ $loop->count }}` for a step indicator. In a nested loop - features inside each plan - `$loop->parent->first` asks whether the **outer** plan is the first one, and `$loop->depth` is `2`. ## Where count, remaining and last go missing `addLoop()` counts the data only when it is **countable and not a `LazyCollection`**. The exclusion is deliberate: counting a lazy collection would run its whole generator (for a cursor, a full database pass) just to fill in `$loop->count`. So for a `LazyCollection`, a generator or any non-countable iterable: - `count` and `remaining` are `null`. - `last` is `null`, so `@if ($loop->last)` **never fires**. - `index`, `iteration`, `first`, `odd`, `even`, `depth` and `parent` still work. If the template needs the last-item treatment, pass an eager collection or array, or handle the ending after the loop. ## Breaking and skipping Blade adds two loop-control directives that accept an optional condition: 1. `@continue($plan->hidden)` skips the current plan. 2. `@break($loop->iteration === 3)` stops after three plans. Without an argument they behave like PHP's `continue` and `break`. Because the loop stack is only popped by `@endforeach`, breaking out early is safe; the compiled code still runs the pop after the loop. ## Interview traps around $loop - Confusing `index` (0-based) with `iteration` (1-based) when printing row numbers. - Assuming `$loop->even` is true on the first row because index 0 is even. - Expecting `$loop` in a `@for` or `@while` loop, where Blade provides none. - Reading `$loop` after `@endforeach`: it then refers to the enclosing loop, or is `null` at the top level. - Relying on `$loop->last` with lazy data, where it is `null`. ## What to keep out of the loop `$loop` is a presentation helper. Deciding which plans the visitor may see, or sorting them, belongs in the controller or a query - the loop should render a prepared list. Also avoid lazy-loading relationships inside the loop body, which turns one page render into a query per row; eager loading is covered in the Eloquent material, not here.

  • Why is $loop->last null when iterating Plan::cursor()?
    `cursor()` returns a `LazyCollection`, and Blade's `addLoop()` counts data only when it is countable and not lazy, to avoid running the whole generator just to know its size. Without a count, `count`, `remaining` and `last` stay `null`. Use an eager collection when the template needs the last-row logic.
  • In nested loops, how do you check whether the outer loop is on its first item?
    Use `$loop->parent->first`. Each `@foreach` pushes its own record, and `parent` holds the enclosing loop's record as an object, so any outer property is reachable. `$loop->depth` tells you the nesting level, starting at 1 for the outermost loop.

saying these in an interview costs you the question

  • $loop->index starts at 1 like iteration
  • $loop->even is true on the first row
  • $loop is available inside @for and @while loops
  • $loop->last works the same for a LazyCollection
  • Keeping a manual counter variable is needed for first and last rows
open as a page

In a Blade view, when should you use @forelse instead of @foreach, and why does @empty mean two different things?

level: juniorimportance: should knowfreq 48%

basics

~20 s

@forelse loops like @foreach but renders its @empty branch when the list has no items. A standalone @empty($x) ... @endempty is a different directive that wraps PHP's empty(), which is false for any object, even an empty Collection.

open as a page

In a Blade view, what do @class, @style, @checked and @selected compile to, and what hand-written template code do they replace?

level: middleimportance: should knowfreq 42%

basics

~20 s

@class and @style take an array of always-on entries plus entry => condition pairs and write a complete class or style attribute; @checked, @selected, @disabled, @readonly and @required print the bare attribute when their condition is true.

open as a page

In Blade, how do @include, @includeIf, @includeWhen, @includeFirst, @includeIsolated and @each differ, especially in which variables the included view can see?

level: middleimportance: should knowfreq 50%

basics

~20 s

@include and its If, When, Unless and First variants hand the partial every parent variable plus extras, differing only in when they render; @includeIsolated passes just the given array, and @each renders a view per item with only that item and its key.

open as a page

On a Laravel pricing page, how would you add a @plan('pro') conditional with Blade::if, and why is it safer than Blade::directive for a per-visitor check?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Blade::if('plan', fn (string $plan) => ...) registers @plan, @elseplan, @unlessplan and @endplan that call your closure through Blade::check() each render; a Blade::directive handler runs once at compile time, so per-visitor logic inside it gets frozen into the cached view.

open as a page