The average mobile app loses 50-60% of users during onboarding. That means more than half of the users who install your app leave before completing setup. Each user who drops out represents wasted acquisition spend and a missed opportunity. This guide covers how to measure, diagnose, and fix onboarding completion problems.
For A/B testing onboarding flows, see A/B Testing Onboarding Flows. For personalization strategies, see Personalized Onboarding Flows with Deep Link Data.
Measuring Onboarding Completion
Define Your Funnel
Before you can improve completion rates, you need to define what "completed onboarding" means. It's not just the last screen of the tutorial:
| App Type | Onboarding Complete When | Why This Point |
|---|---|---|
| E-commerce | First product view + account created | Ready to browse and buy |
| Social | Profile created + first connection | Ready to engage |
| Productivity | First project/document created | Experienced core value |
| Fintech | Account verified + first deposit | Regulatory + activation |
| Gaming | Tutorial completed + first real game | Understands mechanics |
Track Every Step
For a comprehensive analytics setup including funnel definitions, cohort analysis, and event taxonomy, see Onboarding Analytics: Measuring Activation Success.
const ONBOARDING_STEPS = [
'app_opened',
'welcome_screen_viewed',
'signup_started',
'signup_completed',
'profile_created',
'permissions_granted',
'first_action_completed',
'onboarding_completed',
];
function trackOnboardingStep(step, metadata) {
analytics.track('onboarding_step', {
step: step,
stepIndex: ONBOARDING_STEPS.indexOf(step),
totalSteps: ONBOARDING_STEPS.length,
source: metadata.source, // organic, ad, referral
platform: metadata.platform, // ios, android
timeFromPreviousStep: metadata.timeDelta,
sessionId: metadata.sessionId,
});
}
Build the Funnel Report
async function getOnboardingFunnel(dateRange) {
const steps = {};
for (const step of ONBOARDING_STEPS) {
steps[step] = await countUniqueUsers('onboarding_step', {
step: step,
dateRange: dateRange,
});
}
// Calculate drop-off between each step
const funnel = [];
for (let i = 0; i < ONBOARDING_STEPS.length; i++) {
const current = steps[ONBOARDING_STEPS[i]];
const previous = i > 0 ? steps[ONBOARDING_STEPS[i - 1]] : current;
funnel.push({
step: ONBOARDING_STEPS[i],
users: current,
dropOff: previous > 0 ? ((previous - current) / previous * 100).toFixed(1) + '%' : '0%',
cumulative: (current / steps[ONBOARDING_STEPS[0]] * 100).toFixed(1) + '%',
});
}
return funnel;
}
Common Drop-Off Points and Fixes
Drop-Off at Signup
Symptoms: 30-50% of users leave at the signup form.
Causes:
- Too many required fields
- No social login options
- Asking for sensitive data too early (phone number, date of birth)
Fixes:
// Minimal signup: email + password only
function MinimalSignup({ onComplete }) {
return (
<Form>
<Input label="Email" type="email" autoFocus />
<Input label="Password" type="password" />
<Button type="submit">Create Account</Button>
<Divider text="or" />
<GoogleSignIn onSuccess={onComplete} />
<AppleSignIn onSuccess={onComplete} />
<GuestOption onPress={() => onComplete({ guest: true })}>
Browse as Guest
</GuestOption>
</Form>
);
}
Allow guest access. Users who see value first are more likely to create an account later.
Drop-Off at Permissions
Symptoms: 20-40% decline push notification or location permissions.
Causes:
- Asking on the first screen with no context
- No explanation of why the permission is needed
- Users don't trust the app yet
Fixes:
Show a pre-permission screen that explains the value:
function PushPermissionScreen({ onAllow, onSkip }) {
return (
<Screen>
<Illustration source={notificationPreview} />
<Heading>Stay in the loop</Heading>
<Text>
Get notified when your order ships, when prices drop on items
you're watching, and when friends invite you to collaborate.
</Text>
<Button onPress={onAllow}>Enable Notifications</Button>
<LinkButton onPress={onSkip}>Not Now</LinkButton>
</Screen>
);
}
Ask permissions after the user has experienced value, not before. Move permission requests to the moment they're relevant (e.g., ask for location when the user taps "Find nearby stores").
Drop-Off at Profile Setup
For a focused guide on diagnosing and fixing drop-off points, see Reducing Onboarding Drop-Off.
Symptoms: Users create accounts but never complete their profiles.
Causes:
- Too many profile fields
- Unclear why profile information is needed
- Upload requirements (avatar, cover photo) feel like too much effort
Fixes:
Make profile setup optional. Collect only what's needed for the app to work:
function ProfileSetup({ onComplete, onSkip }) {
return (
<Form>
<Input label="Display Name" required />
<OptionalSection label="Optional: Help us personalize your experience">
<AvatarUpload />
<Select label="Industry" options={industries} />
<TagPicker label="Interests" options={interests} />
</OptionalSection>
<Button type="submit">Continue</Button>
<LinkButton onPress={onSkip}>Skip for Now</LinkButton>
</Form>
);
}
Drop-Off at First Action
Symptoms: Users complete setup but never perform the core action (create first project, make first purchase, post first content).
Causes:
- Unclear what to do next
- Empty state is confusing
- Feature overwhelm
Fixes:
Guide users to their first action with a clear, single CTA:
function FirstActionPrompt({ appType }) {
const prompts = {
productivity: {
title: 'Create your first project',
description: 'Start with a template or create from scratch.',
cta: 'Create Project',
screen: 'NewProject',
},
social: {
title: 'Find your friends',
description: 'See who you know on [App].',
cta: 'Find Friends',
screen: 'FindFriends',
},
ecommerce: {
title: 'Explore trending items',
description: 'See what others are buying right now.',
cta: 'Start Browsing',
screen: 'Trending',
},
};
const prompt = prompts[appType];
return (
<Card>
<Heading>{prompt.title}</Heading>
<Text>{prompt.description}</Text>
<Button onPress={() => navigation.navigate(prompt.screen)}>
{prompt.cta}
</Button>
</Card>
);
}
Re-Engagement for Incomplete Onboarding
Identify Incomplete Users
async function getIncompleteOnboardingUsers() {
return await db.query(`
SELECT user_id, last_completed_step, last_active_at
FROM users
WHERE onboarding_completed = false
AND created_at > NOW() - INTERVAL 7 DAY
AND last_active_at < NOW() - INTERVAL 24 HOUR
ORDER BY last_active_at DESC
`);
}
Send Re-Engagement Deep Links
Use deep links to bring users back to exactly where they left off:
async function sendOnboardingReminder(user) {
const nextStep = getNextOnboardingStep(user.lastCompletedStep);
const deepLink = getStepDeepLink(nextStep);
await sendPushNotification(user.id, {
title: getReengagementTitle(nextStep),
body: getReengagementBody(nextStep),
data: { deepLink: deepLink },
});
analytics.track('onboarding_reminder_sent', {
userId: user.id,
nextStep: nextStep,
daysSinceLastActive: daysSince(user.lastActiveAt),
});
}
function getStepDeepLink(step) {
const deepLinks = {
signup_completed: '/onboarding/profile',
profile_created: '/onboarding/permissions',
permissions_granted: '/onboarding/first-action',
first_action_completed: '/onboarding/complete',
};
return deepLinks[step] || '/onboarding';
}
function getReengagementTitle(step) {
const titles = {
signup_completed: 'Finish setting up your profile',
profile_created: 'One more step to get started',
permissions_granted: 'Ready to try your first [action]?',
first_action_completed: 'You are almost done',
};
return titles[step] || 'Continue setting up';
}
Email Sequences for Incomplete Onboarding
const incompleteOnboardingEmails = [
{
delay: '24h',
subject: 'Finish setting up your [App] account',
template: 'onboarding_reminder_1',
includeDeepLink: true,
},
{
delay: '72h',
subject: 'Your account is waiting',
template: 'onboarding_reminder_2',
includeDeepLink: true,
},
{
delay: '7d',
subject: 'Need help getting started?',
template: 'onboarding_help_offer',
includeDeepLink: true,
includeHelpLink: true,
},
];
Each email includes a deep link that opens the app directly to the user's next onboarding step. No login required (if the session is still valid), no navigating through the app.
Onboarding by Acquisition Source
Different acquisition sources produce users with different onboarding completion rates:
| Source | Typical Completion Rate | Why |
|---|---|---|
| Referral | 65-80% | Trust + social motivation |
| Organic search | 40-60% | High intent but no context |
| Paid ads (targeted) | 35-50% | Moderate intent |
| Paid ads (broad) | 20-35% | Low intent, high volume |
| App store browse | 30-45% | Exploratory, less committed |
Customize by Source
Use deep link data to adapt onboarding length and content per source:
function getOnboardingConfig(source) {
switch (source) {
case 'referral':
return {
steps: 3, // Abbreviated
showReward: true,
skipFeatureTour: true,
};
case 'ad_campaign':
return {
steps: 4,
showRelevantFeature: true, // Based on ad creative
skipFeatureTour: false,
};
case 'organic':
default:
return {
steps: 5, // Full onboarding
showFeatureTour: true,
showValueProp: true,
};
}
}
Benchmarks
Industry Averages
| Category | Onboarding Completion Rate |
|---|---|
| Social media | 50-65% |
| E-commerce | 45-60% |
| Productivity | 35-50% |
| Fintech | 30-45% |
| Health/fitness | 40-55% |
| Gaming | 60-75% |
Target Improvements
Realistic improvement targets based on optimization effort:
| Optimization | Expected Lift | Effort |
|---|---|---|
| Reduce signup fields (5 to 2) | +15-25% | Low |
| Add social login | +10-20% | Medium |
| Add guest access | +20-30% | Medium |
| Pre-permission screens | +10-15% | Low |
| Source-based customization | +15-25% | Medium |
| Re-engagement deep links | +5-10% (recovered users) | Medium |
| Progressive onboarding | +10-20% | High |
Monitoring Dashboard
Track these metrics weekly:
const onboardingDashboard = {
completion_rate: 'Users who completed / Users who started',
avg_time_to_complete: 'Median time from install to completion',
step_drop_off: 'Per-step drop-off rates',
source_breakdown: 'Completion rates by acquisition source',
platform_split: 'iOS vs Android completion rates',
recovery_rate: 'Users who returned after incomplete onboarding',
time_to_recovery: 'How long until recovered users come back',
};
Review the funnel weekly. Even a 1% improvement in completion rate compounds over thousands of installs.
For analytics features, see Tolinku analytics. For onboarding use cases, see the onboarding documentation. For analytics setup, see the analytics docs.
Get deep linking tips in your inbox
One email per week. No spam.