AI-powered crash analysis is now available on all plans — including Free.Read the crash analysis guide

Mobile A/B Test Error Tracking: Catch Experiment Failures

NFNourin Mahfuj Finick··8 min read

Every mobile team loves a good A/B test — until a variant crashes on 40% of users and nobody notices for three days. If you're running experiments in production without mobile a/b test error tracking, you're flying blind. A simple remote config change can push a malformed JSON payload, trigger an unhandled exception in a new code path, and send your crash-free rate plummeting — all while your A/B testing dashboard happily reports "statistically significant" results from the 60% of users who didn't crash.

The problem is that most crash reporting tools treat every crash the same. They don't know whether a NullPointerException came from your control group or your bold new checkout flow experiment. Without experiment-aware error tracking, you can't answer the one question that matters: Is my A/B test breaking the app?

Why A/B Tests Are Silent Crash Vectors

A/B tests introduce risk in ways that standard feature releases don't. When you ship a new feature behind a feature flag, your entire user base gets the same binary — it's just toggled on or off. But with A/B testing, multiple variants coexist simultaneously, each exercising different code paths, different UI layouts, and different data payloads.

Common failure modes include:

  • Remote config deserialization failures. Firebase Remote Config returns a JSON string that your app expects to parse into a data class. One malformed value in the treatment variant, and every user in that cohort hits a crash on app startup.
  • Variant-specific resource references. Your experiment references a drawable or string resource that only exists in the latest app version, but 20% of your users haven't updated yet.
  • Third-party SDK incompatibility. The experiment toggles a new analytics SDK integration that conflicts with an existing library in ways your staging environment never caught.
  • Race conditions in variant initialization. Control-group users get the default UI flow that's been battle-tested for months. Treatment users hit a new async initialization path that doesn't handle configuration latency gracefully.

According to LaunchDarkly's experimentation guide, experiments without proper guardrail metrics can silently degrade user experience even when primary metrics look positive. A 5% increase in conversion means nothing if your crash rate doubled in the treatment group.

Tagging Crashes with Experiment Metadata

The foundation of experiment-aware error tracking is metadata. Every crash report needs to answer three questions:

  1. Which experiment was the user enrolled in?
  2. Which variant were they assigned to?
  3. What was the state of the experiment flags at crash time?

Here's how to attach this context on both platforms.

Android: Firebase Remote Config + Crashlytics

Firebase Remote Config powers a significant portion of mobile A/B tests. Pairing it with Crashlytics custom keys gives you variant-level crash visibility.

// Attach experiment metadata to every crash report
val remoteConfig = Firebase.remoteConfig
val experimentId = remoteConfig.getString("checkout_redesign_experiment")
val variant = remoteConfig.getString("checkout_redesign_variant")
 
Firebase.crashlytics.setCustomKey("experiment_id", experimentId)
Firebase.crashlytics.setCustomKey("experiment_variant", variant)
Firebase.crashlytics.setCustomKey(
    "experiment_params",
    remoteConfig.getString("checkout_redesign_params")
)

The key insight is to set these custom keys immediately after fetching and activating remote config — before any experiment-dependent UI renders. If the crash happens during view inflation, you'll still capture which variant caused it. Firebase's crash reporting documentation confirms that custom keys persist across sessions once set.

iOS: LaunchDarkly + Bugspulse

On iOS, LaunchDarkly remains the dominant experimentation platform. Integrating with a crash reporter like Bugspulse means you can segment crashes by experiment variant in real time.

import LaunchDarkly
 
// After LD client initializes, attach experiment context
LDClient.get()?.allFlags.forEach { flag in
    if flag.key.hasPrefix("experiment_") {
        Bugspulse.setAttribute(flag.key, value: flag.value)
    }
}
 
// Also capture the full feature flag snapshot at crash time
let flagSnapshot = LDClient.get()?.allFlags
    .filter { $0.key.hasPrefix("experiment_") }
    .mapValues { String(describing: $0.value) }
Bugspulse.setAttribute("ld_flag_snapshot", value: flagSnapshot)

The hasPrefix("experiment_") convention is critical — you don't want to flood your crash reports with every feature flag in your codebase, only the ones actively involved in A/B tests. LaunchDarkly's iOS SDK reference recommends namespacing experiment flags for exactly this reason.

Variant-Level Crash Rate Monitoring

Tagging crashes is step one. Step two is comparing crash rates across variants. A properly instrumented experiment dashboard should show:

Metric Control (A) Treatment (B)
Sessions 50,000 50,000
Crashes 12 47
Crash-free rate 99.976% 99.906%
ANRs 3 31

That 0.07% difference might look small, but across millions of users, Treatment B is producing nearly 4x more crashes. Without variant-level monitoring, you'd see an aggregate crash rate that looks healthy and completely miss the experiment-induced regression.

This is where experiment error tracking diverges from standard progressive rollout monitoring. Progressive rollouts use feature flags to gradually expose a single new feature — you're watching for an absolute crash rate threshold across all users. A/B tests are fundamentally different: you're comparing two or more groups simultaneously, and statistical methods matter. A treatment variant might only be shown to 10% of users, meaning a 3x crash increase in that cohort barely moves your aggregate numbers. Without per-variant segmentation, you won't see the fire until it's already spread.

The Optimizely experimentation platform encourages teams to define "guardrail metrics" — secondary metrics that must not degrade during an experiment. Crash rate and ANR rate should be non-negotiable guardrails on every test.

Setting Up Auto-Rollback Triggers

Manual monitoring doesn't scale when you're running dozens of experiments. Auto-rollback is the safety net that kills a failing experiment before it impacts statistical significance — or your app store rating.

A robust auto-rollback system needs:

  1. A threshold. "If the treatment variant's crash-free rate drops below 99.5%, kill the experiment."
  2. A minimum sample size. Don't roll back on 2 crashes out of 100 sessions — wait for statistical confidence.
  3. A notification channel. When an experiment is auto-killed, alert the owning team via Slack, PagerDuty, or email.
  4. An audit trail. Log why the experiment was terminated so the team can debug the root cause.

Here's a simplified architecture using Firebase Remote Config and Cloud Functions:

// Cloud Function triggered by Crashlytics issue velocity
exports.detectExperimentCrashSpike = functions.analytics.event('crashlytics_issue')
  .onPublish(async (event) => {
    const experimentId = event.customKeys?.experiment_id;
    if (!experimentId) return;
 
    // Fetch crash stats for this experiment across variants
    const variantStats = await getVariantCrashStats(experimentId);
 
    for (const [variant, stats] of Object.entries(variantStats)) {
      if (stats.crashFreeRate < 0.995 && stats.sampleSize > 1000) {
        // Kill the experiment — set rollout to 0% for treatment
        await killExperimentVariant(experimentId, variant);
        await sendSlackAlert(experimentId, variant, stats);
        console.log(`Auto-killed experiment ${experimentId}, variant ${variant}`);
      }
    }
  });

Statsig's experimentation documentation recommends setting auto-kill thresholds conservatively — a false positive rollback costs you experiment data, but a false negative means users keep crashing.

Building an Experiment Health Dashboard

Your crash reporter should give you a real-time view of experiment health. At minimum, the dashboard needs:

  • Variant comparison view: Side-by-side crash-free rates, ANR rates, and session counts for control vs. treatment.
  • Crash spike timeline: A graph showing crash velocity over the experiment's lifetime, with annotations for when variants were activated or modified.
  • Affected user breakdown: OS version, device model, and app version distribution for each variant's crashes — to catch platform-specific experiment bugs.
  • Experiment lifecycle events: When was the experiment started, modified, ramped, or killed? Overlay these on the crash timeline.

The Bugspulse platform provides variant-level segmentation out of the box when you tag crashes with experiment attributes, making this dashboard trivial to build without custom infrastructure.

CI/CD Integration for Experiment Validation

The best time to catch an experiment crash is before it reaches production. Build experiment validation into your CI pipeline:

# Example: GitHub Actions workflow for experiment smoke testing
experiment-smoke-test:
  runs-on: macos-latest
  steps:
    - name: "Fetch active experiments from LaunchDarkly"
      run: |
        curl -H "Authorization: $LD_API_KEY" \
          https://app.launchdarkly.com/api/v2/flags/production \
          | jq '.items[] | select(.experiments.count > 0)'
 
    - name: "Run UI tests against each experiment variant"
      run: |
        xcodebuild test \
          -scheme MyApp \
          -destination 'platform=iOS Simulator,name=iPhone 16' \
          EXPERIMENT_VARIANTS=all
 
    - name: "Verify crash-free test run"
      run: |
        // Fail if any variant produced a crash in test logs
        grep -q "CRASH" test-output.log && exit 1 || exit 0

Running UI tests against every active experiment variant in CI catches the most common failure mode — variant-specific UI crashes — before your users ever see them.

Don't Experiment with Your Crash Rate

A/B testing is one of the most powerful tools in mobile development, but it's also one of the riskiest when you lack proper error tracking. Every experiment you ship without variant-level crash monitoring is a bet you're placing with user trust.

The fix isn't complicated: tag your crashes with experiment metadata, monitor crash-free rates per variant, and automate rollbacks when things go wrong. Whether you're using Firebase Remote Config, LaunchDarkly, Optimizely, or Statsig, the pattern is the same — instrument, monitor, and protect.

Ready to bring experiment-aware crash reporting to your mobile app? Bugspulse gives you variant-level crash segmentation, real-time experiment health dashboards, and automatic anomaly detection — so your next A/B test improves conversion without tanking your crash rate. Start your free trial today.