← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Loop Style Preferences

style PHP 7.4+ Beginner
debt(d3/e1/b1/t3)
d3 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'default linter catches the common case' (d3), phpcs/phpmd/rector can flag for-with-count patterns and suggest foreach conversions via standard rules.

e1 Effort Remediation debt — work required to fix once spotted

Closest to 'one-line patch or single-call swap' (e1), the quick_fix is a localized loop rewrite — swapping a for($i=0;...) for a foreach is a single-line/single-block change.

b1 Burden Structural debt — long-term weight of choosing wrong

Closest to 'minimal commitment' (b1), this is a per-loop stylistic choice with no cross-cutting structural weight; it's a naming/convention-level decision.

t3 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'minor surprise' (t3), the main gotcha cited in common_mistakes is the foreach-by-reference leak after the loop — a documented but bounded edge case rather than a contradictory behavior.

About DEBT scoring →

Also Known As

iteration style for vs foreach loop choice

TL;DR

Choose the clearest loop construct for the job: foreach for collections, for with index when position matters, while for condition-driven iteration.

Explanation

Loop style is a readability decision. PHP offers foreach, for, while, and do-while, plus functional alternatives like array_map, array_filter, and array_reduce. The right choice depends on intent, not personal habit.

Use foreach when iterating over a collection and you only need the elements (and optionally the key). It is the default — it cannot run off the end, does not need a counter, and reads as 'for each item, do this.' Use a classic for loop only when you genuinely need an index, a step other than 1, or simultaneous traversal of related sequences. Use while when the termination condition is not tied to a collection's length (reading a stream, polling a queue, walking a linked structure). Use do-while when the body must execute at least once.

Functional alternatives (array_map, array_filter, array_reduce) express transformation intent rather than mechanics. They shine when the operation is a pure pipeline: 'take these orders, keep paid ones, sum totals.' They are not a universal replacement — multi-step loops, early exits, side effects, and stateful accumulation usually read better as foreach. Recursion is appropriate for genuinely recursive data (trees, nested structures) but is rarely the right choice for flat iteration in PHP because there is no tail-call optimisation.

Within a file, stay consistent. Mixing for ($i=0; $i<count($xs); $i++) with foreach over the same kind of data in adjacent functions is jarring. Avoid common anti-patterns: calling count() inside the for condition on a static array, using foreach with a by-reference variable and forgetting to unset it, or reaching for array_map purely to look clever when foreach would be plainer.

The guiding principle: the loop construct should communicate what the loop is doing. If a reader has to study the header to understand intent, you picked the wrong construct.

Common Misconception

Functional alternatives like array_map are always more 'modern' and therefore better than foreach. In practice, foreach is often clearer for anything beyond a single pure transformation, and PHP's array functions have measurable overhead and weaker debuggability.

Why It Matters

Loop choice is one of the most frequent micro-decisions in any codebase; the wrong construct adds noise (indices you never use, counters that drift) and the right one makes intent visible at a glance.

Common Mistakes

  • Using for ($i=0; $i<count($items); $i++) when foreach ($items as $item) would do — the index is unused and count() may even be recomputed each iteration.
  • Forgetting to unset() a by-reference variable after foreach ($items as &$item), causing the reference to leak into the next loop.
  • Reaching for array_map/array_filter chains when the operation has side effects, early returns, or accumulates state — foreach reads better there.
  • Mixing while (list($k, $v) = each(...)) legacy patterns with modern foreach in the same file.
  • Using recursion for flat iteration in PHP where it adds stack frames without expressing recursive intent.
  • Carrying over legacy while(list($k,$v)=each(...)) patterns when modernising old code instead of converting them to foreach (each() was removed in PHP 8.0).

Avoid When

  • Performance-critical inner loops where a specific construct has measured wins — micro-style consistency comes second.
  • Translating algorithms from pseudocode or another language where the original loop shape aids review and verification.
  • Working in a legacy codebase with a strong existing convention — match it rather than introduce a second style.

When To Use

  • Choosing between for, foreach, while in new code where any would work syntactically.
  • Refactoring index-based loops that never use the index into foreach for clarity.
  • Deciding whether a transformation belongs in a foreach or in an array_map/filter/reduce pipeline.
  • Establishing or documenting a team style guide for iteration patterns.

Code Examples

✗ Vulnerable
<?php
// Index never used — foreach would be clearer
for ($i = 0; $i < count($orders); $i++) {
    echo $orders[$i]->total() . "\n";
}

// count() recomputed every iteration
for ($i = 0; $i < count($rows); $i++) {
    process($rows[$i]);
}

// Reference leak — $item still references last element
foreach ($items as &$item) {
    $item = strtoupper($item);
}
// later...
foreach ($items as $item) { /* surprise: writes to last element */ }

// array_map abused for side effects
array_map(function ($u) {
    sendEmail($u);
    log("sent to {$u->email}");
}, $users);
✓ Fixed
<?php
// Collection iteration — foreach states intent
foreach ($orders as $order) {
    echo $order->total() . "\n";
}

// Genuine need for index
for ($i = 0, $n = count($rows); $i < $n; $i++) {
    if ($i > 0) echo ", ";
    echo $rows[$i]->name;
}

// Reference loop with explicit cleanup
foreach ($items as &$item) {
    $item = strtoupper($item);
}
unset($item);

// Condition-driven: while is correct
while (($line = fgets($handle)) !== false) {
    process($line);
}

// Pure transformation: array_map fits
$totals = array_map(fn($o) => $o->total(), $orders);

// Side effects belong in foreach
foreach ($users as $user) {
    sendEmail($user);
    log("sent to {$user->email}");
}

Added 22 May 2026
Edited 23 May 2026
Views 21
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 2 pings F 2 pings S 1 ping S 2 pings M 1 ping T 2 pings W 2 pings T 0 pings F 1 ping S 0 pings S 0 pings M 0 pings T 0 pings W
No pings yet today
No pings yesterday
Perplexity 6 Google 2 ChatGPT 2 Ahrefs 1 SEMrush 1 Meta AI 1
crawler 10 crawler_json 3
DEV INTEL Tools & Severity
🟢 Low ⚙ Fix effort: Low
⚡ Quick Fix
Default to foreach for collections; use for only when you need the index or a non-unit step; use while for condition-driven loops; reach for array_map/filter/reduce only for pure pipelines.
📦 Applies To
PHP 7.4+ web cli queue-worker library
🔗 Prerequisites
🔍 Detection Hints
for\s*\(\s*\$\w+\s*=\s*0\s*;\s*\$\w+\s*<\s*count\(
Auto-detectable: ✓ Yes phpcs phpmd rector
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: Low Context: Function

✓ schema.org compliant