PHP's 8 scalar types: bool, int, float, string, null, never, void, mixed
Concept
PHP has a precisely defined set of types that every variable and function return value can belong to. Memorizing them — and more importantly understanding their semantics and edge cases — is foundational to reading type signatures in modern PHP code.
The scalar types
bool: The simplest type — true or false. Internally stored as a single byte. In loose mode, many values coerce to bool (this is PHP's "truthy/falsy" system, covered in depth in the type juggling lesson). In function signatures, bool is explicit.
int: A signed integer stored in 64 bits on 64-bit systems. Range: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 (PHP_INT_MIN to PHP_INT_MAX). No unsigned integers exist in PHP. Overflow wraps to float — PHP does NOT throw an overflow error.
float: A double-precision IEEE 754 floating-point number (64 bits). Also called double in older PHP docs (they are the same type). Precision is approximately 15-16 decimal digits. Do NOT use float for money — store as integer cents.
string: A sequence of bytes in PHP, not a sequence of Unicode characters. PHP strings are binary-safe — they can hold any byte value including null bytes. Length is in bytes, not characters (critical for multibyte text).
The null type
null: Represents the absence of a value. Every type in PHP can be made nullable with ?Type or Type|null. null is also a valid standalone type declaration in PHP 8.2+.
The special types
void: A return type declaration meaning "this function does not return a value." Using return $value in a void function is a compile-time error. Calling return; (empty return) is allowed. PHP 7.1+.
never: PHP 8.1+. A return type meaning "this function never returns normally" — it either throws an exception, calls exit(), or enters an infinite loop. Unlike void, the function doesn't even reach a return statement. Used for functions like redirectAndExit(). The never type is the bottom type — it is a subtype of every type.
mixed: PHP 8.0+. Explicit declaration that a value can be of any type. Equivalent to the old PHP behavior of having no type declaration. Using mixed is better than no declaration because it communicates intentionality — you've consciously chosen to accept any type.
Type hierarchy summary
| Type | Size | Notes |
|---|---|---|
bool | 1 byte | true/false |
int | 8 bytes (64-bit) | No unsigned, no overflow exception |
float | 8 bytes | IEEE 754 double |
string | Variable | Byte string, binary-safe |
null | 0 bytes | Absence of value |
void | N/A | Return type only |
never | N/A | Return type only, PHP 8.1+ |
mixed | N/A | Any type, PHP 8.0+ |
Code Example
<?php
declare(strict_types=1);
// All scalar types demonstrated with their quirks
// bool
$active = true;
$deleted = false;
var_dump((bool) 0); // false
var_dump((bool) ""); // false
var_dump((bool) "0"); // false (this one surprises people)
var_dump((bool) []); // false (empty array)
var_dump((bool) "false"); // TRUE — non-empty non-"0" string is always true
// int
$userId = 42;
$negative = -100;
echo PHP_INT_MAX . "\n"; // 9223372036854775807
echo PHP_INT_MIN . "\n"; // -9223372036854775808
$overflow = PHP_INT_MAX + 1;
var_dump($overflow); // float(9.2233720368548E+18) — silent overflow to float!
// float
$price = 19.99;
$scientific = 1.5e3; // 1500.0
echo PHP_FLOAT_EPSILON; // 2.2204460492503E-16 — smallest detectable difference
echo PHP_FLOAT_MAX; // 1.7976931348623E+308
// string — binary safe
$text = "Hello";
$binary = "\x00\x01\x02"; // Contains null bytes — valid PHP string
$utf8 = "Héllo";
echo strlen($utf8); // 6 bytes, not 5 characters (é is 2 bytes in UTF-8)
echo mb_strlen($utf8); // 5 characters (correct for Unicode)
// null
$maybeUser = null;
var_dump($maybeUser === null); // true
var_dump(isset($maybeUser)); // false (null is treated as "not set" by isset)
// void — function that doesn't return
function logMessage(string $message): void
{
error_log($message);
// return; is OK
// return $message; would be a compile error
}
// never — function that never returns normally
function fail(string $message): never
{
throw new \RuntimeException($message);
// The function MUST throw or exit — no return is possible
}
// mixed — any type accepted
function debugDump(mixed $value): void
{
var_dump($value);
}Interview Q&A
Q: What is the never return type in PHP 8.1 and when would you use it?
never indicates that a function will never return to the caller — it either throws an exception unconditionally or calls exit()/die(). This is more precise than void (which means "returns but with no value"). The never type enables better static analysis: if you have a match expression with an arm that calls abort(), tools like PHPStan can infer that code after that arm is unreachable. Common use cases: a fail(string $message): never helper that always throws, a redirectWithExit(string $url): never that calls header() and exit(), and custom assertion methods. PHPStan and Psalm use never to prune dead code branches in their type narrowing.
Q: Why is PHP's string type called "binary-safe" and what are the implications?
PHP strings are sequences of bytes, not sequences of characters. A PHP string can contain any byte value from \x00 to \xFF, including null bytes (\x00). Functions like strlen() count bytes, not characters. This means strlen("é") returns 2 on a UTF-8 system because é is encoded as two bytes (0xC3 0xA9), not 1. The "binary-safe" label means you can store arbitrary binary data (images, serialized objects, encrypted blobs) in a PHP string without corruption. The implication: always use mb_* functions (mb_strlen, mb_substr, mb_strtolower) when working with human-readable text that may contain multibyte characters.
Q: What happens when a PHP integer overflows PHP_INT_MAX?
PHP silently converts the result to a float instead of wrapping around or throwing an error. PHP_INT_MAX + 1 gives a float value 9.2233720368548E+18. This is different from C (undefined behavior), Java (wraps to negative), or Python (arbitrary precision). The practical danger: if you compute a large ID, bitwise operation result, or hash and it exceeds PHP_INT_MAX, you silently lose integer precision as the float only has ~15 significant decimal digits. For arbitrary-precision integers, use the GMP extension: gmp_add('9223372036854775807', '1') returns a GMP object representing the exact value.