Question
I understand what foreach is, what it does, and how to use it. My question is specifically about how foreach works internally in PHP.
For a long time, I assumed that foreach iterated over the original array itself. Later, I found many explanations saying that it works on a copy of the array instead, so I assumed that was the full story. However, after some experiments, that explanation seems incomplete.
Assume we start with this array:
$array = array(1, 2, 3, 4, 5);
Test case 1
foreach ($array as $item) {
echo "$item\n";
$array[] = $item;
}
print_r($array);
/* Output in loop: 1 2 3 4 5
$array after loop: 1 2 3 4 5 1 2 3 4 5 */
This suggests that foreach is not iterating directly over the live array structure, because otherwise the loop would keep growing forever.
Test case 2
foreach ($array as $key => $item) {
$array[$key + 1] = $item + 2;
echo "$item\n";
}
print_r($array);
/* Output in loop: 1 2 3 4 5
$array after loop: 1 3 4 5 6 7 */
This again suggests that the values being iterated are not simply read back from the array after modification.
Test case 3
var_dump(each($array));
foreach ($array as $item) {
echo "$item\n";
}
var_dump(each($array));
/* Output
array(4) {
[1]=>
int(1)
["value"]=>
int(1)
[0]=>
int(0)
["key"]=>
int(0)
}
1
2
3
4
5
bool(false)
*/
This seems to show that after foreach, the array's internal pointer is at the end.
Test case 4
foreach ($array as $key => $item) {
echo "$item\n";
each($array);
}
/* Output: 1 2 3 4 5 */
Test case 5
foreach ($array as $key => $item) {
echo "$item\n";
reset($array);
}
/* Output: 1 2 3 4 5 */
These tests suggest that changing the array pointer manually inside the loop does not affect iteration.
So what is foreach really doing internally?
- Does it iterate over a copy of the array?
- Does it interact with the original array’s internal pointer?
- Why does the pointer appear to end up at the end after the loop?
- Can pointer functions such as
each()orreset()ever affect aforeachloop’s behavior?
Short Answer
By the end of this page, you will understand the real model behind PHP foreach: it does not simply mean “loop over a full copy of the array,” and it also does not behave like while (each(...)). You will learn how foreach behaves when iterating by value vs by reference, how array modifications affect the loop, why internal pointer functions can be misleading, and what rules are safe to follow in real PHP code.
Concept
foreach in PHP is a dedicated iteration construct for arrays and traversable objects. The key idea is:
foreachmaintains its own iteration state.- It is not controlled by
reset(),next(),each(), or the array's user-visible internal pointer. - It may use PHP's internal array structure efficiently rather than making a naive element-by-element PHP-level copy.
A common beginner explanation is that foreach “loops over a copy of the array.” That explanation is useful in some cases, but it is not the full internal truth.
What foreach really guarantees
When you write:
foreach ($array as $value) {
// ...
}
PHP determines how to iterate through the elements in a way that is stable for the loop. In normal by-value iteration:
- the loop visits the elements that were part of the iteration sequence,
- appending or modifying the array during the loop usually does not make the loop continue over newly added elements,
- changing the traditional array pointer with functions like
reset()does not drive the loop.
When you iterate by reference:
foreach ($array as &$value) {
// ...
}
behavior changes, because now the loop variable is linked directly to the actual array element.
Why this matters
In real programs, developers often:
- filter arrays,
- transform values,
- validate request data,
- build query parameters,
- normalize configuration arrays.
If you misunderstand foreach, you may accidentally:
- expect new elements to be visited when they are not,
- corrupt data by using references incorrectly,
- assume
reset()ornext()can control aforeachloop, - introduce bugs when mutating an array during iteration.
Historical note
Older PHP versions had details that caused confusion, especially around internal pointers and copy-on-write behavior. The safest modern understanding is:
foreachhas its own iteration mechanism,- it is separate from pointer-based array functions,
- modifying the iterated array during the loop can produce different results depending on whether iteration is by value or by reference.
So the best mental rule is not “foreach always uses a copy,” but rather:
foreachcreates a stable iteration context, and PHP handles the underlying array efficiently behind the scenes.
Mental Model
Think of foreach like reading names from an attendance list.
By value
When iterating by value, it is like the teacher starts class with a prepared list of students to call:
- if someone writes extra names on the whiteboard during class, the teacher usually does not start calling those new names,
- if someone changes the seating position, it does not change the prepared calling order.
By reference
When iterating by reference, it is more like the teacher is not only reading the list but also editing the official roster directly as they go.
That means:
- changes affect the real data immediately,
- the loop variable stays connected to the actual array slot,
- mistakes can have side effects after the loop finishes.
Internal pointer functions
Functions like reset() and each() are like moving a separate bookmark in the same book.
foreach is not reading based on that bookmark. It has its own place-tracking system. So moving the bookmark does not usually change what foreach reads next.
Syntax and Examples
Basic syntax
Iterate by value
foreach ($array as $value) {
echo $value;
}
Iterate with keys and values
foreach ($array as $key => $value) {
echo $key . ': ' . $value . PHP_EOL;
}
Iterate by reference
foreach ($array as &$value) {
$value *= 2;
}
unset($value); // important
Example 1: normal by-value iteration
$numbers = [1, 2, 3];
foreach ($numbers as $n) {
echo $n . PHP_EOL;
$numbers[] = $n * 10;
}
print_r($numbers);
Output:
1
2
3
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 10
[4] => 20
[5] => 30
)
What this shows
- The loop prints only the original
1, 2, 3. - The appended values are added to the array.
- The loop does not keep iterating forever.
Example 2: iterate by reference
$numbers = [1, 2, 3];
foreach ($numbers as &$n) {
$n *= 2;
}
unset($n);
print_r($numbers);
Output:
Array
(
[0] => 2
[1] => 4
[2] => 6
)
What this shows
$nis a reference to each real array element.- Changing
$nchanges the array itself. unset($n)is important so$ndoes not remain a reference to the last element.
Example 3: pointer functions do not control foreach
$numbers = [10, 20, 30];
reset($numbers);
foreach ($numbers as $n) {
echo $n . PHP_EOL;
next($numbers);
}
The loop still prints all values in normal foreach order. Calling next() here does not drive the loop.
Step by Step Execution
Consider this example:
$items = [1, 2, 3];
foreach ($items as $key => $value) {
echo "$key => $value" . PHP_EOL;
$items[] = $value + 100;
}
print_r($items);
Step-by-step trace
Before the loop
$items = [1, 2, 3];
The array contains three elements.
First iteration
key = 0value = 1- output:
0 => 1 - then this runs:
$items[] = 101;
Now the real array becomes:
[1, 2, 3, 101]
But the loop continues according to its own iteration state.
Second iteration
key = 1value = 2- output:
1 => 2 - append
102
Array becomes:
[1, 2, 3, 101, 102]
Third iteration
key = 2value = 3- output:
2 => 3 - append
103
Array becomes:
[1, 2, 3, 101, 102, 103]
Loop ends
The loop stops after the original iteration sequence is exhausted.
Final result
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 101
[4] => 102
[5] => 103
)
Key lesson
The array does change during the loop, but foreach by value does not simply chase every newly appended element. That is why appending inside the loop does not create an infinite loop in this example.
Real World Use Cases
foreach is one of the most common constructs in PHP because arrays are used everywhere.
1. Processing request data
foreach ($_POST as $field => $value) {
echo $field . ': ' . trim($value) . PHP_EOL;
}
Used for:
- sanitizing form input,
- validating required fields,
- building error lists.
2. Formatting API responses
foreach ($users as $user) {
$result[] = [
'id' => $user['id'],
'name' => strtoupper($user['name'])
];
}
Used when transforming database or API data into response payloads.
3. Updating configuration values
foreach ($config as $key => $value) {
if ($value === null) {
$config[$key] = 'default';
}
}
Used for normalizing settings before an application starts.
4. Batch file or job processing
foreach ($jobs as $job) {
processJob($job);
}
Used in scripts, queues, cron jobs, and import/export tools.
5. Building HTML or CLI output
foreach ($messages as $message) {
echo '- ' . $message . PHP_EOL;
}
Used in templates, reports, logs, and console commands.
Real Codebase Usage
In real codebases, foreach is usually used in predictable, low-surprise ways.
Common patterns
Guard clauses before looping
if (empty($users)) {
return [];
}
foreach ($users as $user) {
// process user
}
This avoids unnecessary work and makes intent clear.
Validation during iteration
$errors = [];
foreach ($input as $field => $value) {
if ($value === '') {
$errors[$field] = 'This field is required.';
}
}
Building a new array instead of mutating the current one
$clean = [];
foreach ($records as $record) {
$clean[] = trim($record);
}
This is often clearer and safer than changing the iterated array.
Updating in place with references only when needed
foreach ($prices as &$price) {
$price = round($price, 2);
}
unset($price);
Use this only when you intentionally want to modify the original array.
Prefer explicit transformations
In modern PHP, developers sometimes choose array_map(), array_filter(), or array_reduce() for transformation-heavy code. But foreach is still preferred when:
- multiple statements are needed,
- validation and branching are involved,
- readability matters more than compactness.
Best practice
In production code, avoid changing the structure of the same array you are currently iterating unless you are certain about the behavior. A safer pattern is:
$newItems = [];
foreach ($items as $item) {
$newItems[] = transform($item);
}
instead of appending into $items while looping over $items.
Common Mistakes
1. Assuming foreach is controlled by reset() or next()
Broken assumption:
$numbers = [1, 2, 3];
foreach ($numbers as $n) {
next($numbers);
echo $n;
}
A beginner may expect this to skip values. It does not work that way.
Avoid it
Use foreach alone, or use pointer-based functions separately. Do not mix the two to control iteration.
2. Modifying the array structure and expecting intuitive results
$numbers = [1, 2, 3];
foreach ($numbers as $n) {
$numbers[] = $n;
}
This changes the array, but the loop behavior may not match your mental model.
Avoid it
If you need a growing result, write to a separate array:
$result = [];
foreach ($numbers as $n) {
$result[] = $n;
}
3. Forgetting unset() after iterating by reference
Broken code:
$numbers = [1, 2, 3];
foreach ($numbers as &$n) {
$n *= 2;
}
foreach ($numbers as $n) {
// unexpected behavior can happen here
}
Why this happens
After the first loop, $n still references the last array element.
Correct version
$numbers = [1, 2, 3];
foreach ($numbers as &$n) {
$n *= 2;
}
unset($n);
foreach ($numbers as $n) {
echo $n . PHP_EOL;
}
4. Expecting by-value iteration to update the array
Broken expectation:
$numbers = [1, 2, 3];
foreach ($numbers as $n) {
$n *= 2;
}
print_r($numbers);
The original array is unchanged.
Why
$n is just a copy of each value.
Correct approach
foreach ($numbers as &$n) {
$n *= 2;
}
unset($n);
5. Using each() in modern PHP code
each() is deprecated and removed in modern PHP versions.
Avoid it
Use foreach for iteration, or explicit index-based loops when needed.
Comparisons
| Concept | How it works | Affects original array values? | Uses internal pointer functions? | Best use case |
|---|---|---|---|---|
foreach ($array as $value) | Iterates by value | No | No | Reading or transforming values safely |
foreach ($array as &$value) | Iterates by reference | Yes | No | Updating array values in place |
for ($i = 0; $i < count($array); $i++) | Index-based loop | Yes, if you assign back | No | Numeric arrays when index control matters |
while (list($k, $v) = each($array)) | Pointer-driven iteration | Yes, depending on code | Yes | Legacy code only |
array_map() | Returns transformed array | No, unless assigned | No | Simple value transformation |
foreach vs pointer-based iteration
foreach
- clearer,
- safer,
- independent of
reset()andnext(), - recommended in modern PHP.
pointer-based functions
- depend on internal pointer position,
- easier to misuse,
- mostly seen in old code.
By value vs by reference
| Style | Loop variable contains | Changes to loop variable affect source array? |
|---|---|---|
as $value | a value copy | No |
as &$value | a reference to the real element | Yes |
Cheat Sheet
Core rules
foreachhas its own iteration state.- It does not rely on
reset(),next(), oreach()to move through elements. - By-value
foreachdoes not modify the source array unless you explicitly assign into the array. - By-reference
foreachlets you modify the original elements. - After by-reference iteration, always call
unset($value).
Syntax
foreach ($array as $value) {
// read values
}
foreach ($array as $key => $value) {
// read keys and values
}
foreach ($array as &$value) {
// modify values in place
}
unset($value);
Safe patterns
$result = [];
foreach ($items as $item) {
$result[] = transform($item);
}
foreach ($items as &$item) {
$item = trim($item);
}
unset($item);
Things to avoid
- Mixing
foreachwitheach()orreset()to control the loop - Appending to the same array and expecting new elements to always be iterated
- Forgetting
unset()after&$value - Assuming
$valuechanges the original array in by-value mode
Quick memory rule
as $value= read a valueas &$value= edit the real element
FAQ
Does PHP foreach loop over a copy of an array?
Not in the simple beginner sense of “PHP duplicates the whole array and loops over that copy every time.” A better explanation is that foreach creates its own stable iteration context.
Does reset() affect a foreach loop?
No. foreach does not use reset() or next() to decide which element comes next.
Why can I change the array inside foreach without an infinite loop?
In normal by-value iteration, foreach does not automatically keep extending its iteration to include everything you append during the loop.
When should I use foreach by reference?
Use it when you intentionally want to modify the original array values in place.
Why do I need unset($value) after foreach (&$value)?
Because the loop variable remains a reference to the last element. If you reuse that variable later, you may accidentally modify the array.
Can foreach be used with objects?
Yes. It works with arrays and objects that are traversable.
Is each() still recommended with foreach?
No. each() is legacy functionality and should not be used in modern PHP code.
Mini Project
Description
Build a small PHP script that demonstrates the difference between iterating an array by value and by reference. This project is useful because many real bugs come from accidentally assuming that foreach updates the original array or from forgetting to clear a reference after the loop.
Goal
Create a script that shows three behaviors: reading values with foreach, modifying values by reference, and safely building a new transformed array.
Requirements
- Start with a numeric array containing at least three values.
- Use one
foreachloop by value and show that the original array does not change. - Use one
foreachloop by reference and update the original array. - Build a separate result array using another
foreachloop. - Print the arrays after each step so the behavior is visible.
Keep learning
Related questions
Are PDO Prepared Statements Enough to Prevent SQL Injection in PHP?
Learn how PDO prepared statements prevent SQL injection in PHP, what they protect, and the mistakes that still leave MySQL apps vulnerable.
Can You Bind an Array to an IN Clause in PHP PDO?
Learn how PDO handles placeholders in IN() clauses, why arrays cannot be bound directly, and the safe PHP pattern to build dynamic queries.
Choosing the Right MySQL Collation for PHP and UTF-8
Learn how MySQL character sets and collations work with PHP, and how to choose a practical UTF-8 setup for web applications.