Use the source, Luke

As usual, the answer lies in the source. PHP uses the following two functions internally to perform the increment and decrement operations:

ZEND_API int increment_function(zval *op1)

ZEND_API int decrement_function(zval *op1)

These operations modify the op1 argument based on its type ( NULL is a type); inside increment_function() you can see the following branch in the code:

case IS_NULL: ZVAL_LONG(op1, 1); break;

The above code changes the type of op1 into a number and sets its value to 1 .

Conversely, decrement_function() offers no such branch and therefore the default action will be performed:

default: return FAILURE;

Running this code won't actually yield any observable failure, because the return values are absorbed in the Zend VM, but the variable definitely isn't updated either.

It's not a bug(tm)

You may be surprised to know that this behaviour, including that for boolean values, is actually documented:

Note: The increment/decrement operators do not affect boolean values. Decrementing NULL values has no effect too, but incrementing them results in 1 .

Regarding booleans:

$a = true; var_dump($a--); // true $a = false; var_dump($a++); // false

Regarding strings: