0

PHP's 8 scalar types: bool, int, float, string, null, never, void, mixed

Beginner5 min read·php-02-002
interview

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

TypeSizeNotes
bool1 bytetrue/false
int8 bytes (64-bit)No unsigned, no overflow exception
float8 bytesIEEE 754 double
stringVariableByte string, binary-safe
null0 bytesAbsence of value
voidN/AReturn type only
neverN/AReturn type only, PHP 8.1+
mixedN/AAny type, PHP 8.0+

Code Example

php
<?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.