Keeping flag evaluations stable

Feature flags are pure functions – same inputs, same outputs. The most common way to break that contract is to change the distinct_id between evaluations, which happens naturally when a user transitions from anonymous to identified.

This page covers how to keep the hash input stable so flags don't flip during authentication transitions. If you haven't already, read identity resolution first – a stable identity strategy eliminates most flag stability problems before they start.

Why the hash input changes

When a user starts anonymous and then logs in:

hash("my-experiment", "anon-uuid-123") → 0.31 → variant "test"
hash("my-experiment", "user-456") → 0.72 → variant "control"

Same person, different variant. The hash function worked correctly both times – it received different inputs and produced different outputs.

For experiments, this is data corruption: the user experienced "test" but may now be counted in "control." For feature rollouts, the user sees a feature appear and then disappear on login.

Start with your identity strategy

The best way to keep flag evaluations stable is to prevent the distinct_id from changing in the first place. The identity resolution guide covers three approaches:

  • Golden path: stable ID from first touch – assign a durable ID before any flag evaluation happens. The distinct_id never changes, so the hash never changes. This is the recommended approach.
  • Common path: anonymous then identify on login – the SDK generates an anonymous ID, then identify() links it to a known user. This changes the distinct_id, which changes the hash. The mitigations below exist for this path.
  • Server-side bootstrap with a known ID – if you have server rendering, you know the user before the page loads. Bootstrap with their stable ID and the anonymous-to-identified transition never happens.

If you can use the golden path or server-side bootstrap, the rest of this page is unnecessary. The sections below are for when the distinct_id will change and you need to manage that.

Flag-specific mitigations

When your identity strategy involves a distinct_id change (the common path), PostHog offers two mechanisms to keep flag values stable across that transition.

Hashes on $device_id instead of distinct_id. Since the device ID doesn't change on identify(), the flag value stays consistent across the authentication boundary.

See Device bucketing for setup.

Device bucketingExperience continuity
Database costNoneRead on every evaluation, write on first identify
Local evaluationSupportedNot supported – forces network round-trip
BootstrappingCompatibleNot compatible
person_profiles: 'identified_only'WorksDoesn't work
Cross-device consistencyNo (per-device)No (per-person, but only after identify)
Known issues$device_id can be lost on cookie clearsposthog-js #2623: values can change after identify

Experience continuity

Pins a stable hash key to a person record in Postgres. Enabled per-flag via ensure_experience_continuity. The server remembers which hash key was used for the first evaluation and reuses it on all subsequent evaluations, even after the distinct_id changes.

See Creating feature flags – persisting across authentication for setup.

Use experience continuity only when device bucketing and the stable ID approaches aren't feasible. It comes at a cost: a database read and write on every evaluation, no support for local evaluation, and a known issue where values can still change after identify().

Cross-device consistency

Device-scoped solutions (device bucketing, cookie-based stable IDs) don't span devices. If a user logs in on their phone and laptop, each device hashes independently.

For cross-device consistency, target flags on person properties instead of relying on the hash:

Flag condition: where plan = "enterprise" → 100% rollout
Flag condition: where cohort = "beta-users" → show feature

Person properties resolve server-side against the person record. The same person matches the same conditions regardless of device. This reframes the question: instead of "how do I make the hash stable across devices," ask "how do I target users based on who they are?"

Choosing the right pattern

RequirementBest approach
Same flag value before and after login (single device)Stable ID or device bucketing
Same flag value across devices (logged-in users)Target on person properties
Same flag value across devices (anonymous users)Not solvable without cross-device identity
Maximum debuggability and controlServer-side local eval with explicit stable ID
Quick fix, minimal code changesDevice bucketing
Can't change identity flow at allExperience continuity

Further reading

Community questions

Was this page useful?

Questions about this page? or post a community question.