Strings7 min readExamples run on PHP 8.5.10

strncmp()

Learn how PHP 8.4 strncmp() compares a limited prefix of two strings, how to read its return value, and which mistakes to avoid.

Signature

strncmp(string $string1, string $string2, int $length): int

Purpose

strncmp() performs a binary-safe, case-sensitive comparison of at most the first $length characters of two strings. It works like strcmp(), but you set an upper limit on how much of each string is examined. The usual uses are prefix checks (does this line start with ERROR:?) and ordering strings by a fixed-width key.

Signature

Parameters

  • $string1: the first string.
  • $string2: the second string.
  • $length: the maximum number of characters to compare from each string. There is no default, so you must always pass it.

The comparison is byte-based, so "character" here means a single byte. That is fine for ASCII. With multibyte text such as UTF-8, a limit can cut through the middle of a character.

Return value and failure cases

The function returns an int:

  • a value less than 0 if $string1 is less than $string2,
  • a value greater than 0 if $string1 is greater than $string2,
  • 0 if the compared portions are equal.

Only the sign of the result is meaningful. Since PHP 8.2 the function is no longer guaranteed to return the difference in string lengths when the lengths differ. It may return -1 or 1 instead. Test with === 0, < 0 or > 0, or normalise with <=> 0, and never rely on the exact magnitude.

A negative $length is not allowed in PHP 8. It throws a ValueError. A $length of 0 compares nothing, so the result is 0.

Example 1: filtering lines by prefix

The most common use is checking whether a string begins with a known marker. Compare the same number of bytes as the marker has, and test for 0.

Example
<?php
$lines = ['ERROR: disk full', 'INFO: started', 'ERROR: timeout'];
$errors = [];
foreach ($lines as $line) {
    if (strncmp($line, 'ERROR:', 6) === 0) {
        $errors[] = $line;
    }
}
echo json_encode($errors, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
Output
["ERROR: disk full","ERROR: timeout"]

The marker ERROR: is six bytes long, so 6 is the right limit. Writing the number by hand is easy to get wrong. If the marker changes, update the number too, or compute it with strlen().

Example 2: reading the sign

The $length limit decides which part of each string takes part in the comparison. Differences after that point are ignored.

Example
<?php
$results = [
    'equal_prefix' => strncmp('apple', 'apricot', 2) <=> 0,
    'less' => strncmp('apple', 'apricot', 3) <=> 0,
    'greater' => strncmp('banana', 'apple', 1) <=> 0,
];
echo json_encode($results, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
Output
{"equal_prefix":0,"less":-1,"greater":1}

With a limit of 2 both strings begin with ap, so they are equal. With a limit of 3 the third bytes are p and r, so the first string is smaller. The <=> 0 turns any negative or positive result into -1 or 1, which keeps the output predictable.

Example 3: edge cases

Example
<?php
echo json_encode([
    'case' => strncmp('Hello', 'hello', 5) <=> 0,
    'short' => strncmp('abc', 'abcdef', 10) <=> 0,
    'limited' => strncmp('abc', 'abcdef', 3),
    'zero' => strncmp('x', 'y', 0),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";

try {
    strncmp('a', 'a', -1);
} catch (ValueError $e) {
    echo get_class($e), "\n";
}
Output
{"case":-1,"short":-1,"limited":0,"zero":0}
ValueError

The uppercase H sorts before lowercase h in byte order, so the case-sensitive comparison is negative. When $length is larger than a string, the comparison stops at the end of the shorter one. If one string is a proper prefix of the other, the shorter one is considered smaller, which is why short is -1. Limiting the comparison to 3 bytes makes abc and abcdef equal. A length of 0 always gives 0, and a negative length throws ValueError.

Edge cases to remember

  • Case sensitivity: strncmp('Hello', 'hello', 5) is not 0. Use strncasecmp() for ASCII case-insensitive comparison.
  • Short strings: if either string has fewer than $length bytes, the comparison covers the shorter string, and a length difference counts as a difference.
  • Empty strings: two empty strings are equal for any non-negative length.
  • Binary data: null bytes are compared like any other byte, so the function is binary safe.

Common mistakes

  1. Using the result as a boolean. if (strncmp($a, $b, 3)) is true when the strings differ, which is the opposite of what many people expect. Compare explicitly with === 0.
  2. Assuming the return value is a difference. Only the sign is meaningful, and PHP 8.2 changed the possible magnitudes.
  3. Using a wrong length. A limit shorter than the marker accepts false matches, and a longer one can reject valid strings.
  4. Expecting case-insensitive matching. The comparison is exact byte by byte.
  5. Passing a negative length. Validate any length that comes from variable data before calling the function.
  • strcmp() compares whole strings in a binary-safe way.
  • strncasecmp() is the case-insensitive version of this function.
  • substr_compare() compares from an offset, up to a given length.
  • substr() extracts part of a string, which you can then compare yourself.
  • strstr() finds the first occurrence of a string.
  • preg_match() handles pattern-based checks.

Recap

strncmp() compares only the first $length bytes of two strings, case-sensitively. A result of 0 means the compared portions match. Treat any other value only as negative or positive, never as an exact distance, and remember that a negative length throws a ValueError.

Tested examples

Filter lines that start with a marker
<?php
$lines = ['ERROR: disk full', 'INFO: started', 'ERROR: timeout'];
$errors = [];
foreach ($lines as $line) {
    if (strncmp($line, 'ERROR:', 6) === 0) {
        $errors[] = $line;
    }
}
echo json_encode($errors, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
Output
["ERROR: disk full","ERROR: timeout"]
Reading the sign of the result
<?php
$results = [
    'equal_prefix' => strncmp('apple', 'apricot', 2) <=> 0,
    'less' => strncmp('apple', 'apricot', 3) <=> 0,
    'greater' => strncmp('banana', 'apple', 1) <=> 0,
];
echo json_encode($results, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
Output
{"equal_prefix":0,"less":-1,"greater":1}
Edge cases: case, short strings, zero and negative length
<?php
echo json_encode([
    'case' => strncmp('Hello', 'hello', 5) <=> 0,
    'short' => strncmp('abc', 'abcdef', 10) <=> 0,
    'limited' => strncmp('abc', 'abcdef', 3),
    'zero' => strncmp('x', 'y', 0),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";

try {
    strncmp('a', 'a', -1);
} catch (ValueError $e) {
    echo get_class($e), "\n";
}
Output
{"case":-1,"short":-1,"limited":0,"zero":0}
ValueError