Net Promoter Score (NPS)

Customer loyalty metric measuring likelihood to recommend your product to others

Overview

Net Promoter Score (NPS) is a customer loyalty and satisfaction metric that measures the likelihood of customers recommending your product or service to others. Based on a single question survey, NPS categorizes customers into Promoters, Passives, and Detractors to provide insights into customer sentiment and business growth potential.

NPS is widely used across industries as a predictor of business growth, customer retention, and word-of-mouth marketing effectiveness. It provides a simple yet powerful way to track customer loyalty trends and benchmark against competitors.

Formula:
NPS = % Promoters (9-10) - % Detractors (0-6)
9-10

Promoters

Loyal customers who will keep buying and refer others

7-8

Passives

Satisfied but unenthusiastic customers

0-6

Detractors

Unhappy customers who can damage your brand

70+

Excellent

50-70

Good

0-50

Average

<0

Needs Improvement

Industry Context

NPS varies significantly by industry. SaaS companies typically see scores of 20-40, while consumer brands often achieve 50+. Always benchmark against your specific industry and track trends over time.

Why It Matters

  • Customer Loyalty: Directly measures customer advocacy and loyalty
  • Growth Predictor: Strong correlation with business growth and expansion
  • Word-of-Mouth: Indicates likelihood of organic customer acquisition
  • Churn Risk: Identifies customers at risk of leaving
  • Product Development: Guides improvement priorities and feature decisions
  • Competitive Benchmarking: Enables comparison with industry standards

How to Measure It

Calculate NPS by surveying customers with the question "How likely are you to recommend our product to a friend or colleague?" on a 0-10 scale, then analyzing response distribution and trends.

Basic NPS Calculation

-- Calculate Net Promoter Score with detailed breakdowns
WITH nps_responses AS (
  SELECT 
    survey_id,
    customer_id,
    survey_date,
    nps_score,
    customer_segment,
    subscription_tier,
    account_age_months,
    CASE 
      WHEN nps_score >= 9 THEN 'Promoter'
      WHEN nps_score >= 7 THEN 'Passive'
      ELSE 'Detractor'
    END as nps_category,
    CASE 
      WHEN nps_score >= 9 THEN 1 ELSE 0
    END as is_promoter,
    CASE 
      WHEN nps_score >= 7 AND nps_score <= 8 THEN 1 ELSE 0
    END as is_passive,
    CASE 
      WHEN nps_score <= 6 THEN 1 ELSE 0
    END as is_detractor
  FROM customer_nps_surveys
  WHERE survey_date >= CURRENT_DATE - INTERVAL '12 months'
    AND nps_score IS NOT NULL
),
nps_calculation AS (
  SELECT 
    DATE_TRUNC('month', survey_date) as survey_month,
    customer_segment,
    subscription_tier,
    COUNT(*) as total_responses,
    SUM(is_promoter) as promoter_count,
    SUM(is_passive) as passive_count,
    SUM(is_detractor) as detractor_count,
    ROUND(100.0 * SUM(is_promoter) / COUNT(*), 1) as promoter_percentage,
    ROUND(100.0 * SUM(is_passive) / COUNT(*), 1) as passive_percentage,
    ROUND(100.0 * SUM(is_detractor) / COUNT(*), 1) as detractor_percentage,
    ROUND(AVG(nps_score), 1) as avg_score,
    ROUND(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY nps_score), 1) as median_score
  FROM nps_responses
  GROUP BY 1, 2, 3
)
SELECT 
  survey_month,
  customer_segment,
  subscription_tier,
  total_responses,
  promoter_count,
  passive_count,
  detractor_count,
  promoter_percentage,
  passive_percentage,
  detractor_percentage,
  ROUND(promoter_percentage - detractor_percentage, 1) as net_promoter_score,
  avg_score,
  median_score,
  CASE 
    WHEN (promoter_percentage - detractor_percentage) >= 70 THEN 'Excellent'
    WHEN (promoter_percentage - detractor_percentage) >= 50 THEN 'Good'
    WHEN (promoter_percentage - detractor_percentage) >= 0 THEN 'Average'
    ELSE 'Needs Improvement'
  END as nps_rating,
  LAG(promoter_percentage - detractor_percentage) OVER (
    PARTITION BY customer_segment, subscription_tier 
    ORDER BY survey_month
  ) as prev_month_nps,
  ROUND((promoter_percentage - detractor_percentage) - 
    LAG(promoter_percentage - detractor_percentage) OVER (
      PARTITION BY customer_segment, subscription_tier 
      ORDER BY survey_month
    ), 1) as nps_change
FROM nps_calculation
WHERE total_responses >= 30  -- Minimum sample size for statistical relevance
ORDER BY survey_month DESC, net_promoter_score DESC;

NPS Trend Analysis

-- Analyze NPS trends and patterns over time
WITH nps_trends AS (
  SELECT 
    nr.customer_id,
    nr.survey_date,
    nr.nps_score,
    nr.nps_category,
    c.signup_date,
    c.subscription_tier,
    c.industry,
    EXTRACT(days FROM AGE(nr.survey_date, c.signup_date)) as days_since_signup,
    LAG(nr.nps_score) OVER (PARTITION BY nr.customer_id ORDER BY nr.survey_date) as prev_nps_score,
    LAG(nr.survey_date) OVER (PARTITION BY nr.customer_id ORDER BY nr.survey_date) as prev_survey_date,
    ROW_NUMBER() OVER (PARTITION BY nr.customer_id ORDER BY nr.survey_date) as survey_sequence
  FROM nps_responses nr
  JOIN customers c ON nr.customer_id = c.customer_id
),
nps_journey_analysis AS (
  SELECT 
    customer_id,
    COUNT(*) as total_surveys,
    MIN(nps_score) as first_nps_score,
    MAX(nps_score) as latest_nps_score,
    ROUND(AVG(nps_score), 1) as avg_nps_score,
    latest_nps_score - first_nps_score as nps_change_total,
    CASE 
      WHEN latest_nps_score > first_nps_score THEN 'Improving'
      WHEN latest_nps_score < first_nps_score THEN 'Declining'
      ELSE 'Stable'
    END as nps_trend,
    STRING_AGG(nps_category, ' -> ' ORDER BY survey_date) as nps_journey,
    COUNT(CASE WHEN nps_category = 'Promoter' THEN 1 END) as promoter_surveys,
    COUNT(CASE WHEN nps_category = 'Detractor' THEN 1 END) as detractor_surveys
  FROM nps_trends
  WHERE total_surveys >= 2  -- Customers with multiple surveys
  GROUP BY customer_id
),
cohort_nps AS (
  SELECT 
    DATE_TRUNC('month', c.signup_date) as signup_cohort,
    nr.survey_date,
    EXTRACT(months FROM AGE(nr.survey_date, c.signup_date)) as months_since_signup,
    COUNT(*) as responses,
    ROUND(AVG(nr.nps_score), 1) as avg_nps,
    COUNT(CASE WHEN nr.nps_category = 'Promoter' THEN 1 END) as promoters,
    COUNT(CASE WHEN nr.nps_category = 'Detractor' THEN 1 END) as detractors,
    ROUND(100.0 * COUNT(CASE WHEN nr.nps_category = 'Promoter' THEN 1 END) / COUNT(*) - 
          100.0 * COUNT(CASE WHEN nr.nps_category = 'Detractor' THEN 1 END) / COUNT(*), 1) as cohort_nps
  FROM nps_responses nr
  JOIN customers c ON nr.customer_id = c.customer_id
  GROUP BY 1, 2, 3
  HAVING COUNT(*) >= 10  -- Minimum responses per cohort
)
SELECT 
  signup_cohort,
  months_since_signup,
  responses,
  avg_nps,
  cohort_nps,
  promoters,
  detractors,
  RANK() OVER (PARTITION BY signup_cohort ORDER BY months_since_signup) as cohort_maturity_rank
FROM cohort_nps
WHERE months_since_signup <= 24  -- First 2 years
ORDER BY signup_cohort DESC, months_since_signup ASC;

NPS Driver Analysis

-- Analyze factors that drive NPS scores
WITH customer_attributes AS (
  SELECT 
    nr.customer_id,
    nr.nps_score,
    nr.nps_category,
    c.subscription_tier,
    c.industry,
    c.company_size,
    c.acquisition_source,
    -- Product usage metrics
    pu.monthly_active_days,
    pu.features_used_count,
    pu.support_tickets_count,
    -- Customer success metrics
    cs.onboarding_completion_rate,
    cs.time_to_value_days,
    cs.health_score,
    -- Financial metrics
    fm.monthly_revenue,
    fm.payment_issues_count
  FROM nps_responses nr
  JOIN customers c ON nr.customer_id = c.customer_id
  LEFT JOIN product_usage pu ON nr.customer_id = pu.customer_id 
    AND DATE_TRUNC('month', pu.usage_date) = DATE_TRUNC('month', nr.survey_date)
  LEFT JOIN customer_success cs ON nr.customer_id = cs.customer_id
  LEFT JOIN financial_metrics fm ON nr.customer_id = fm.customer_id 
    AND DATE_TRUNC('month', fm.transaction_date) = DATE_TRUNC('month', nr.survey_date)
),
nps_correlations AS (
  SELECT 
    'Product Usage - Active Days' as factor,
    CORR(nps_score, monthly_active_days) as correlation_coefficient,
    ROUND(AVG(CASE WHEN nps_category = 'Promoter' THEN monthly_active_days END), 1) as promoter_avg,
    ROUND(AVG(CASE WHEN nps_category = 'Detractor' THEN monthly_active_days END), 1) as detractor_avg
  FROM customer_attributes
  WHERE monthly_active_days IS NOT NULL
  
  UNION ALL
  
  SELECT 
    'Features Used Count' as factor,
    CORR(nps_score, features_used_count) as correlation_coefficient,
    ROUND(AVG(CASE WHEN nps_category = 'Promoter' THEN features_used_count END), 1) as promoter_avg,
    ROUND(AVG(CASE WHEN nps_category = 'Detractor' THEN features_used_count END), 1) as detractor_avg
  FROM customer_attributes
  WHERE features_used_count IS NOT NULL
  
  UNION ALL
  
  SELECT 
    'Support Tickets Count' as factor,
    CORR(nps_score, -support_tickets_count) as correlation_coefficient,  -- Negative because more tickets = lower NPS
    ROUND(AVG(CASE WHEN nps_category = 'Promoter' THEN support_tickets_count END), 1) as promoter_avg,
    ROUND(AVG(CASE WHEN nps_category = 'Detractor' THEN support_tickets_count END), 1) as detractor_avg
  FROM customer_attributes
  WHERE support_tickets_count IS NOT NULL
  
  UNION ALL
  
  SELECT 
    'Customer Health Score' as factor,
    CORR(nps_score, health_score) as correlation_coefficient,
    ROUND(AVG(CASE WHEN nps_category = 'Promoter' THEN health_score END), 1) as promoter_avg,
    ROUND(AVG(CASE WHEN nps_category = 'Detractor' THEN health_score END), 1) as detractor_avg
  FROM customer_attributes
  WHERE health_score IS NOT NULL
),
segment_nps AS (
  SELECT 
    subscription_tier,
    industry,
    company_size,
    acquisition_source,
    COUNT(*) as total_responses,
    ROUND(AVG(nps_score), 1) as avg_nps_score,
    COUNT(CASE WHEN nps_category = 'Promoter' THEN 1 END) as promoters,
    COUNT(CASE WHEN nps_category = 'Detractor' THEN 1 END) as detractors,
    ROUND(100.0 * COUNT(CASE WHEN nps_category = 'Promoter' THEN 1 END) / COUNT(*) - 
          100.0 * COUNT(CASE WHEN nps_category = 'Detractor' THEN 1 END) / COUNT(*), 1) as segment_nps
  FROM customer_attributes
  GROUP BY 1, 2, 3, 4
  HAVING COUNT(*) >= 20  -- Minimum sample size
)
SELECT 
  factor,
  ROUND(correlation_coefficient, 3) as correlation,
  promoter_avg,
  detractor_avg,
  ROUND(promoter_avg - detractor_avg, 1) as difference,
  RANK() OVER (ORDER BY ABS(correlation_coefficient) DESC) as correlation_rank
FROM nps_correlations
WHERE correlation_coefficient IS NOT NULL
ORDER BY ABS(correlation_coefficient) DESC;

Survey Best Practices

Send NPS surveys at key moments (post-purchase, quarterly check-ins), keep them short, follow up with qualitative questions for context, and ensure representative sampling across customer segments.

Best Practices

1. Survey Design

  • Use the standard 0-10 likelihood to recommend question
  • Include follow-up questions for qualitative insights
  • Keep surveys short and focused
  • Ensure mobile-friendly survey design

2. Timing Strategy

  • Survey at meaningful customer journey moments
  • Avoid survey fatigue with appropriate frequency
  • Consider seasonal and business cycle impacts
  • Time surveys after positive interactions when possible

3. Response Analysis

  • Segment Analysis: Break down NPS by customer segments
  • Trend Monitoring: Track changes over time
  • Driver Analysis: Identify factors influencing scores
  • Text Analysis: Analyze qualitative feedback for themes

4. Action Planning

  • Follow up with Detractors to address concerns
  • Activate Promoters for referrals and testimonials
  • Focus on converting Passives to Promoters
  • Implement systematic improvement processes

5. Organizational Integration

  • Share NPS results across all teams
  • Connect NPS to business outcomes and KPIs
  • Use NPS insights for product development
  • Benchmark against industry standards