String Variants
A/B test string values by attaching an experiment to a key: deterministic, stateless, cross-platform selection driven by an assignment id, with exposure events you forward to your own analytics.
Variants let you A/B test the value of a string. You attach an experiment to one string key; the experiment carries traffic-allocation weights and a text value per variant. When a variant is selected, it overrides the base value for that key. Everywhere else the base value is served unchanged.
The user-facing feature is called variants; the API object is an experiment. One experiment attaches to exactly one key. You create and run experiments from the terminal — see CLI variants.
How selection works
Selection is driven by a stable assignment id you set on the SDK:
setAssignmentId(id)The id is typically a user id or device id — anything stable per person. Setting it recomputes selection immediately from the already-verified current bundle: no server round-trip, no local state to persist. Passing null clears the assignment and serves base values.
Selection is deterministic and stateless, and byte-identical across every platform. The same assignment id resolves to the same variant on iOS, Web, Android, and React Native. Bucketing:
bucket = BE_uint32(SHA-256(UTF8(experimentId + ":" + assignmentId))[0..3]) mod 100This yields a bucket in 0..99. Allocation names are sorted lexicographically by Unicode code point; weights are accumulated; the first name where bucket < acc is selected.
Locale is deliberately not part of the hash input. The same assignment resolves to the same variant across every locale, so a user's experience is consistent no matter which language they see.
Per-SDK method names and code live on the SDK pages: iOS, Web, Android, React Native.
Exposure and analytics
The SDK fires one exposure event per resolved experiment read. Exposures are deduped per SDK-instance session on key + experimentId + variant + assignmentId. Locale is excluded from the dedupe key, so a user counts once per experiment regardless of how many locales they read. Delivery is asynchronous — the exposure is queued after the string read returns, never inline.
Each event carries exactly these fields:
| Field | Meaning |
|---|---|
key | The string key the experiment is attached to |
experimentId | The experiment that resolved |
variant | The selected variant name (e.g. control) |
locale | The locale the string was read in |
assignmentId | The assignment id in effect at read time |
You forward these to your own analytics pipeline. The SDK ships no telemetry of its own — it never phones home.
Signing and soft-fail
Experiment definitions are covered by a separate experiments_signature, Ed25519-verified independently and after the main bundle signature. This keeps variants on the same no-unsigned-content guarantee as the rest of delivery — see Bundles, signing, and the CDN.
Variants never block strings and never serve an unverified value. On any failure the SDK soft-fails to base values and still accepts and serves the bundle:
- No assignment id set
- Invalid allocation
- Missing value for the selected variant
- A bad or absent
experiments_signature - An old, pre-variants bundle
An SDK version without variants support reads a variants bundle as if the experiments block weren't there and simply serves base values.
Limits
Enforced server-side when you define an experiment:
- At most 10 variants per experiment.
- Variant names match
^[a-z0-9_]{1,64}$.controlis a reserved name. - Variant values are at most 10000 characters.
- MVP is
textformat only —icukeys are rejected server-side.
Availability
Variants shipped in iOS 1.1.0, Web 1.1.0, Android 1.1.0, and React Native 0.2.0. It is an additive, backward-compatible minor release: existing integrations compile and run unchanged, and an old SDK reading a variants bundle simply serves base values.