update_option returns false when the value has not changed

update_option() returns false for two entirely different situations: the write failed, and the write was unnecessary. It compares the new value against the current one with === and returns early if they match, so a settings screen that treats the return as success reports an error to anyone who pressed Save without changing anything.

// everything read back out of wp_options is a string unless it was serialised
update_option( 'catalogue_cache_ttl', '24' );

update_option( 'catalogue_cache_ttl', '24' );   // false — identical, no write
update_option( 'catalogue_cache_ttl', 24 );     // true  — int !== string, writes

// so this reports a failure that did not happen
if ( ! update_option( 'catalogue_cache_ttl', $ttl ) ) {
    add_settings_error( 'catalogue', 'save', 'Could not save settings.' );
}

The comparison being strict cuts both ways. An integer setting stored once comes back as a string, so passing the integer again writes a row that has not changed on every save — harmless, and it means the “no change, no write” optimisation never actually fires for that option. Passing the string returns false and reads as an error. Neither is a bug in update_option(); both are bugs in code that reads its return as “saved”. Cast on the way in so the type is stable, and where you genuinely need to know, read the option back. add_option() has the same shape for a different reason — it returns false when the option already exists. Worth knowing too: no write means update_option_{$option} and updated_option do not fire, so a cache invalidated from one of those hooks silently does not get invalidated.