`strpos` in PHP: Stop Getting Bitten by the Zero-Position Bug
Why `strpos` returns false AND 0, and how to check correctly. A production-hardened guide to the classic PHP footgun.
strpos in PHP: Stop Getting Bitten by the Zero-Position Bug
strpos in PHP is deceptively simple on the surface, quietly destructive when misused. It's a classic footgun, and I still see the mistake in code reviews. Let's sort it out properly.
What strpos Does
strpos finds the position of the first occurrence of a substring within a string. The signature is straightforward:
It returns the integer position of the first match, starting from zero. If no match is found, it returns false.
Simple example:
The Bug
The natural instinct is to write:
This fails when the substring appears at position zero — the very start of the string. In that case, strpos returns 0, which is falsy in PHP:
The substring was found. strpos returned 0. But your condition was false.
The Fix: Strict Comparison with !== false
The correct pattern is to compare strictly against false:
Use !== false (strict, type-safe) rather than != false (loose). With strict comparison, 0 (integer) and false (boolean) are distinct — which is what you need.
Practical Example: Detecting Hashtags
A common real-world use:
Wrapping the check in a typed function keeps calling code clean and removes the !== false noise from every call site.
PHP 8+: str_contains for Simple Checks
If you're on PHP 8 or above, use str_contains for simple existence checks:
str_contains returns a plain boolean, so there's no zero-false ambiguity. Use it when you only need to know whether the substring exists.
Use strpos when you need the position:
Watch Out in switch Statements
The same zero-false confusion appears in switch blocks, which use loose equality by default:
With loose comparison, 0 == false is true, so the false case matches both "not found" and "found at position zero". Restructure the logic or use match (PHP 8+), which uses strict comparison:
Quick Reference
| Scenario | Use |
|---|---|
| Need to know if substring exists (PHP 8+) | str_contains() |
| Need the position of the substring | strpos() !== false |
| Case-insensitive search | stripos() !== false |
| Last occurrence | strrpos() !== false |
| Multibyte strings (UTF-8) | mb_strpos() !== false |
Summary
strpos is reliable and fast, but its return type requires strict handling. Always use !== false when checking the result in a conditional. On PHP 8+, reach for str_contains when position doesn't matter. Stay alert to loose comparison traps in switch blocks — the same zero-equals-false confusion applies there too.
Damian Hodgkiss
Senior Staff Engineer at Sumo Group, leading development of AppSumo marketplace. Technical solopreneur with 25+ years of experience building SaaS products.