Question

PHP String Increment in for Loops: Why a to z Continues

Question

Why does the following PHP loop continue printing values such as aa, ab, and yz instead of stopping after z? How can it be changed to print only the letters A through Z?

<?php
for ($i = 'a'; $i <= 'z'; $i++) {
    echo "$i\n";
}

Short Answer

You will learn how PHP increments alphabetic strings, why the loop condition still accepts values such as aa, and how to use numeric loops with chr() or range() when you need exactly one alphabet letter at a time.

Concept

PHP's increment operator (++) does more than add one to numbers. When applied to an ASCII letter string, it follows an alphabetic carry rule:

  • 'a'++ becomes 'b'
  • 'y'++ becomes 'z'
  • 'z'++ becomes 'aa'
  • 'az'++ becomes 'ba'

That makes the values resemble spreadsheet columns or an odometer. Therefore, incrementing 'z' does not produce a value that ends the loop; it produces 'aa'.

The condition also uses string comparison:

$i <= 'z'

For non-numeric strings, PHP compares them lexicographically (dictionary-like, character by character). A value beginning with a through y, such as 'aa' or 'yz', compares as less than 'z'. The loop finally stops when the incremented value becomes 'za', because 'za' is greater than 'z'.

This matters because code that appears to use letters as a simple sequence is actually using two string features at once: string incrementing and string comparison. They are usually less clear than using numeric indexes for a fixed alphabet range.

Mental Model

Think of a string being incremented as an odometer with letters instead of digits.

  • After a, the next value is b.
  • After z, there is no next single-letter position, so PHP adds another position: aa.
  • After az, the rightmost letter rolls over to a and the left letter advances: ba.

The loop's stop sign says, “continue while the value is no greater than z.” Values beginning with a through y are still before z, so the stop sign does not stop the loop immediately after z.

Take Quiz

Syntax and Examples

Use a numeric counter when you want a known number of letters. ord() converts a character to its character code, and chr() converts a character code back to a character.

<?php
for ($code = ord('A'); $code <= ord('Z'); $code++) {
    echo chr($code) . PHP_EOL;
}

This starts at the code for A, increases one number at a time, and stops at the code for Z. chr($code) turns each code back into its corresponding letter.

For this exact task, range() is also concise:

<?php
foreach (range('A', 'Z') as $letter) {
    echo $letter . PHP_EOL;
}

range('A', 'Z') creates an array containing the one-letter strings from A to Z.

The original behavior can be seen directly:

<?php
$letter = 'z';
$letter++;

echo $letter; // aa

Step by Step Execution

Consider a smaller version of the original loop:

<?php
for ($i = 'x'; $i <= 'z'; $i++) {
    echo $i . ' ';
}

Execution proceeds as follows:

  1. $i starts as 'x'. Since 'x' <= 'z', PHP prints x.
  2. $i++ changes 'x' to 'y'.
  3. 'y' <= 'z', so PHP prints y.
  4. $i++ changes 'y' to 'z'.
  5. 'z' <= 'z', so PHP prints z.
  6. $i++ changes 'z' to 'aa', not to a value after z in the usual single-letter sense.
  7. 'aa' <= 'z' is true because a comes before z, so PHP prints aa.
  8. The loop continues through values such as ab, az, ba, and yz.
  9. After 'yz', string increment produces 'za'. Since 'za' > 'z', the condition is false and the loop ends.

A numeric version has a much more direct trace:

<?php
for ($code = ord('X'); $code <= ord('Z'); $code++) {
    echo chr($code) . ' ';
}

It prints X Y Z because the numeric values increase until they exceed the code for Z.

Real World Use Cases

Alphabetic sequences appear in several practical situations:

  • Spreadsheet-style labels: Generate A, B, ..., Z, AA, AB for columns or sections. PHP string increment can be useful when this extended sequence is actually desired.
  • Multiple-choice labels: Display answer labels from A to D or A to Z. A numeric loop plus chr() makes the fixed limit explicit.
  • Report headings: Create numbered sections with alphabetic subheadings.
  • Data imports: Map column positions to letters while reading or exporting CSV and spreadsheet-like data.
  • Test data: Generate predictable one-letter category codes for fixtures or demonstrations.

Choose the approach based on the requirement: use a fixed numeric range for exactly A–Z, and use an extended label generator if AA, AB, and beyond are valid output.

Take Quiz

Real Codebase Usage

In production code, developers usually avoid relying on a string's ++ behavior for a fixed alphabet range. Numeric boundaries are easier to review and less likely to produce accidental extra labels.

A reusable helper makes intent clear:

<?php
function alphabetLetters(string $start = 'A', string $end = 'Z'): array
{
    if (strlen($start) !== 1 || strlen($end) !== 1) {
        throw new InvalidArgumentException('Start and end must each be one character.');
    }

    if (ord($start) > ord($end)) {
        return [];
    }

    $letters = [];

    for ($code = ord($start); $code <= ord($end); $code++) {
        $letters[] = chr($code);
    }

    return $letters;
}

print_r(alphabetLetters('A', 'D'));

Common project patterns include:

  • Validation: Check that inputs are single letters before converting them with ord().
  • Guard clauses: Return early when the start boundary is greater than the end boundary.
  • Data transformation: Use range('A', 'Z') with array_map() or foreach to build select options, labels, or configuration values.
  • Explicit bounds: Store the number of choices or the final letter in a named variable instead of relying on an implicit string rollover rule.

For spreadsheet-style labels beyond Z, use a dedicated function and test boundary cases such as Z → AA and AZ → BA.

Common Mistakes

Assuming z++ produces an invalid or terminal value

<?php
$letter = 'z';
$letter++;
echo $letter; // aa

PHP treats alphabetic strings as incrementable sequences. Do not use 'z' as a stopping boundary if string incrementing is also used and only single letters are allowed.

Expecting <= 'z' to mean “one letter up to z”

<?php
for ($i = 'a'; $i <= 'z'; $i++) {
    echo $i;
}

The comparison is about string order, not string length. Values such as 'aa' can still compare as less than 'z'.

Using range() when you need labels beyond Z

<?php
print_r(range('A', 'Z'));

This is correct for one-letter labels, but it does not create AA, AB, and later spreadsheet-style labels. Use a specific multi-letter label algorithm when that is the actual requirement.

Mixing uppercase and lowercase boundaries

<?php
for ($code = ord('a'); $code <= ord('Z'); $code++) {
    echo chr($code);
}

The start and end codes are in the wrong order, so the loop does not run. Keep the same case, such as A to Z or a to z.

Forgetting that ASCII ranges contain non-letters between Z and a

Do not attempt to print both uppercase and lowercase letters with one continuous character-code loop from A to z. That interval includes punctuation characters such as [, \, and _. Use two separate ranges instead.

Comparisons

ApproachBest forResult after ZNotes
String increment: $letter++Spreadsheet-like labelsAAConvenient for extended labels, but boundaries need care.
Numeric loop with ord() and chr()A precise character-code rangeLoop ends at the chosen codeExplicit and suitable for exactly A–Z.
range('A', 'Z')A simple fixed list of lettersArray ends at ZReadable when you want to iterate over the letters.
Array of labelsCustom or non-contiguous labelsDefined by the arrayBest for labels such as A, B, D, N/A.

for is useful when you are advancing a numeric index. foreach is usually clearer when you already have the values to process, such as the array returned by range().

Take Quiz

Cheat Sheet

  • $value++ can increment alphabetic ASCII strings in PHP.
  • 'a'++ → 'b'
  • 'z'++ → 'aa'
  • 'az'++ → 'ba'
  • String comparisons such as 'aa' <= 'z' are lexicographic for non-numeric strings.
  • Use this for exactly uppercase letters:
for ($code = ord('A'); $code <= ord('Z'); $code++) {
    echo chr($code) . PHP_EOL;
}
  • Or iterate over a fixed range:
foreach (range('A', 'Z') as $letter) {
    echo $letter . PHP_EOL;
}
  • Do not use a continuous ord('A') to ord('z') range; it includes non-letter ASCII characters.
  • Use string increment deliberately when AA, AB, and later labels are expected.

FAQ

Why does PHP turn z into aa?

PHP applies an alphabetic carry rule when incrementing ASCII letter strings. The z rolls over to a, and a new leading a is added.

Why is aa less than or equal to z in PHP?

Non-numeric strings are compared lexicographically. Since the first a in aa comes before z, aa compares as less than z.

How do I print only A through Z in PHP?

Use range('A', 'Z') in a foreach, or loop from ord('A') through ord('Z') and convert each value with chr().

Is for ($i = 'A'; $i <= 'Z'; $i++) safe for printing A to Z?

No. After Z, PHP increments the value to AA, which can still satisfy the string comparison and continue the loop.

Can I use string increment for spreadsheet column names?

Yes, it can produce a sequence such as A through Z, then AA, AB, and so on. Still, put an explicit, tested ending rule around it.

Does range('A', 'Z') include AA after Z?

No. It creates the fixed one-character range from A through Z.

Should I use "\n" or PHP_EOL for new lines?

"\n" is common for output intended for Unix-style text. PHP_EOL uses the platform's native line ending and is often convenient in command-line scripts.

Take Quiz

Mini Project

Description

Build a command-line-style letter label generator for a quiz, report, or form. It should create exactly the requested uppercase labels without accidentally continuing to AA after Z.

Goal

Create a function that returns uppercase labels from a starting letter through an ending letter, with validation for invalid ranges.

Requirements

Requirement 1

Take Quiz

Keep learning

Related questions

View more