The never Return Type in PHP 8.1: Functions That Never Return
AI generated
8.4
PHP · never Return Type · PHP 8.1
The never Return Type
Functions that are guaranteed to never return

void means a function does not produce a useful return value, but still returns to the caller normally. never means something fundamentally different: the function never returns control at all, it always throws an exception or terminates the script. Since PHP 8.1, that distinction can be expressed directly in the signature.

9 min read Type System PHP 8.1+

1. What never means and how it differs from void

void describes a function that does not produce a return value, but returns to the caller normally after execution, control flow simply continues right after the function call. never describes a function that never returns control at all: either it is guaranteed to throw an exception, or it terminates the process through exit or die.

That difference is not a mere formality, it changes what the caller is allowed to assume about the code after the function call. After a void call, the following code still has to account for every possible state, after a never call, the following code is simply unreachable from the type system's point of view.

This distinction exists in many statically typed languages under different names, for example as a bottom type or never type, and describes the same concept there: a type for which there can by definition be no valid instance and no normal return from a function.

2. When a function should declare never instead of void

never belongs in a signature whenever a function is guaranteed, without exception, to either throw an exception or terminate the script through exit or die, with no code path that returns normally instead. Even a single path with a regular return makes never the wrong choice.

Typical candidates are pure error handling functions like throwNotFound(), assertion helpers that always throw on a failed condition, or bootstrap scripts that immediately terminate the program on a fatal configuration error.


function throwNotFound(string $resource): never
{
    throw new NotFoundException("Resource not found: {$resource}");
}

3. Syntax and rules: never cannot be combined

Unlike most types, never cannot be combined with other types into a union type, because never already represents the empty set of all possible return values. An expression like never|string makes no logical sense and is rejected by the parser.

A function declared as never that actually has a path with a regular return, whether with or without a value, causes a fatal error at runtime as soon as that path executes. The compiler does not statically verify that guarantee for completeness, only external analysis tools do.


// Fatal error at runtime once this path executes: never must not return
function validate(int $value): never
{
    if ($value < 0) {
        throw new InvalidArgumentException('Value must be non-negative');
    }

    return; // invalid: this path returns normally
}

4. The benefit for static analysis

The real value of never does not show up at runtime, it shows up in tools like PHPStan and Psalm: as soon as an analysis recognizes a call to a never function, it marks every piece of code right after it as unreachable and reports it, instead of silently ignoring it.

That is especially valuable for type narrowing: if a variable is narrowed to a specific type right before a never call, the analysis tool can reliably assume, after the call, that the excluded case no longer exists at all, because control flow never leaves that branch normally.


function process(?User $user): void
{
    if ($user === null) {
        throwNotFound('user');
    }

    // PHPStan narrows $user to User here, the null case is provably unreachable
    echo $user->getName();
}

5. never in abstract methods and interfaces

never can also be used in abstract methods and interface declarations to mandate that every implementation must never return normally. That is useful for contracts like a FatalHandlerInterface, where every concrete implementation is guaranteed to either throw an exception or terminate the program.

Importantly, an implementation may narrow the return type, but never widen it, so never in an interface signature forces every implementation to also use never, or something even more specific, deviating to void would be a contract violation and a type compatibility error.

6. Interaction with the throw expression since PHP 8.0

Since PHP 8.0, throw is an expression rather than a plain statement, which allows using throw inside a null coalescing expression or a ternary operator, for example. never complements that feature at the function level, attributing the same guarantee to an entire function that throw provides for a single expression.

Combining both features lets you build a compact helper function that is itself declared never and internally uses throw as an expression to throw different kinds of errors depending on a condition, without multi line if blocks.

7. Common mistakes: never on functions that sometimes do return

The most common mistake with never happens when a function is meant to always throw in theory, but has an overlooked path in practice, for example a switch statement without a default branch that simply falls through on an unexpected value and implicitly returns null.

These mistakes stay undetected at compile time and only show up as a fatal error at runtime once the missing path actually executes. A match expression instead of switch reduces this risk noticeably, because match automatically throws an UnhandledMatchError on a missing case instead of silently falling through.


function dispatch(string $action): never
{
    match ($action) {
        'delete' => throw new ForbiddenException('Action not allowed'),
        'legacy' => exit('Legacy action terminated'),
        // No default needed: match throws UnhandledMatchError automatically
    };
}

8. Practical example: never in a router and dispatcher

In a simple router, never fits naturally on the method that reacts to a missing route. Instead of modeling a 404 response as a regular return value, the method throws a specific exception, which an outer error handler turns into an actual HTTP response.

The benefit shows up directly in the calling code: after calling notFound(), no additional check is needed, neither a return value check nor nullable handling, because the type system itself guarantees that this path never continues normally.


final class Router
{
    public function dispatch(string $path): Response
    {
        foreach ($this->routes as $route) {
            if ($route->matches($path)) {
                return $route->handle();
            }
        }

        $this->notFound($path); // never returns, no code after this needs a null check
    }

    private function notFound(string $path): never
    {
        throw new RouteNotFoundException("No route matches: {$path}");
    }
}

9. never combined with exit and middleware stacks

Besides exceptions, exit or die is the second legitimate way to satisfy a never guarantee, for example in a CLI script that should terminate immediately with an exit code on a fatal configuration error, without the rest of the call stack continuing.

In middleware stacks, caution is needed: a middleware declared never permanently interrupts the entire processing chain, there is no way for subsequent middleware to still intervene. never should therefore only be used for truly terminal failure cases, not for regular control flow decisions inside a pipeline.

A useful thought experiment in practice: anyone wondering whether a middleware really should never return normally should check whether there is a single conceivable production case where the request could still be processed further despite the failure, because even one such case argues against never.

Feature void never
Does the function return? Yes, without a return value No, never
Code after the call Keeps executing normally Considered unreachable
Combinable with union types Yes, in certain contexts No, never combinable
Typical use case Setters, logging methods Error handling, exit, assertion helpers
Error on violation No error, void tolerates any lack of return value Fatal error on a regular return
Benefit for type narrowing No special effect Analysis tools reliably exclude the case

Mironsoft

PHP modernization, code quality, and legacy refactoring

Grown PHP code nobody wants to touch anymore?

We modernize PHP codebases to current language standards, introduce static analysis and coding standards, and refactor legacy code step by step without endangering live operations.

Legacy Refactoring

Modernize grown PHP code in a structured, low-risk way.

Establishing Code Quality

Anchor PHPStan, coding standards, and CI checks sustainably in the team.

Version Upgrades

Plan and execute PHP major version upgrades safely, without downtime.

10. Summary

never Return Type

Core idea

never signals that a function is guaranteed to never return control to the caller normally.

Distinction

void returns without a value, never does not return at all, it throws or terminates the process.

Benefit

PHPStan and Psalm reliably mark code after a never call as unreachable.

Caution

A single regular return path in a never function causes a fatal error at runtime.

11. FAQ: never Return Type

1What is the difference between void and never?
void returns to the caller normally without a return value, never never returns at all, it always throws an exception or terminates the program.
2Since which PHP version does the never type exist?
The never return type was introduced in PHP 8.1, alongside enums, readonly properties, and several other features.
3Can never be combined with other types?
No, never cannot be combined in a union type, since it already represents the empty set of all possible return values.
4What happens if a never function returns anyway?
As soon as the code path with a regular return executes, PHP throws a fatal error at runtime, the compiler does not statically verify that itself.
5What is never useful for in static analysis?
PHPStan and Psalm mark code right after a never call as unreachable and use the guarantee for more precise type narrowing.
6Can an abstract method declare never?
Yes, and every implementation is then required to also honor never, deviating to void would be a contract violation.
7Should every exception throwing function use never?
Only if the function always throws without exception or terminates the program, a single normal return path makes never the wrong choice.
8How does never relate to the throw expression?
throw as an expression since PHP 8.0 gives the same guarantee for a single expression that never describes for an entire function.
9Is never suitable for middleware stacks?
Only for truly terminal failure cases, since a never middleware permanently interrupts the entire processing chain with no chance for further handling.
10Does a match expression help with never functions?
Yes, match automatically throws an UnhandledMatchError on a missing case, which makes overlooked paths in never functions noticeably less likely.