Two analysts can look at the same dataset and report numbers that are 2x apart, both correct. The arithmetic mean adds everything up and divides by the count. The median sorts and takes the middle. On skewed data, the gap between them is where business decisions go wrong. On a clean, symmetric distribution they land in roughly the same place. On any real dataset with outliers, they diverge, and the gap is where business decisions go wrong.
In This Article
This article walks through two worked examples that show the divergence in practice, then a short takeaway on which metric to reach for.
Worked example 1: how big is a “typical” American city?
Take the U.S. Census Bureau’s 2020 population data, filtered to incorporated cities with at least 25,000 residents. That’s roughly 1,200 places.
import pandas as pd
cities = pd.read_csv('us_cities_2020.csv')
mean_pop = round(cities.population_2020.mean(), 0)
median_pop = round(cities.population_2020.median(), 0)
For this set:
- Mean: ~105,000 residents
- Median: ~42,000 residents
The mean is 2.5× the median. The reason is the right tail. New York City carries 8.3M residents into the average. Los Angeles adds 3.9M. Chicago, Houston, Phoenix each contribute over a million. There are only a handful of cities that size, but each one pulls the mean up by orders of magnitude more than a median city does.

If a policy analyst says “the average American city has 105,000 people,” they’re technically correct and practically misleading. If you were sizing infrastructure grants for a “typical” city, designing for 105,000 residents would overspec for more than 80% of the cities you’d serve. The same mistake appears in product analytics: designing a campaign threshold around mean basket size overspecs for the minority of high-spend customers. The “average city” they’re describing doesn’t exist. Most cities cluster well below it. Median is the honest answer when distribution is skewed: a typical city in the U.S. has about 42,000 people, and that’s the population a planner sizing infrastructure should target.
Worked example 2: when the average customer kills the marketing campaign
A regional grocery chain pulled six months of receipt data. The analyst grouped by customer and visit, then summed each basket:
df = pd.read_excel('receipts.xlsx')
basket = (
df[['customer_id', 'transaction_id', 'line_total']]
.groupby(['customer_id', 'transaction_id'])
.line_total
.sum()
.reset_index(name='basket_total')
)
mean_basket = basket.basket_total.mean()
median_basket = basket.basket_total.median()
The numbers:
- Mean basket: $58
- Median basket: $36
- Gap: $22 per visit
Marketing built a promotion off the mean. The pitch was: “spend $60 or more on a single visit, get 20% off a $10 add-on item.” The reasoning was clean — the average basket is $58, so most customers are a small nudge away from $60. Push that nudge, capture the $10 add-on, win incremental margin.
The campaign flopped. Three months in, redemption was under 4% of distributed coupons.
The reason was the median. The average basket is $58 because a small group of large-family shoppers post $200+ tickets every week. The median customer rings up $36 and walks out — they aren’t a small nudge from $60, they’re a $24 gap from it, and twenty percent off a $10 item doesn’t close that. The campaign was designed for a customer who didn’t exist in volume.

If they’d designed against the median — “spend $40, get the discount” — the campaign would have moved most of the distribution. The metric pick decided the outcome.
When mean inflates, when median grounds you
The pattern across both examples is the same: skewed distributions plus an extreme right tail. In every business dataset we work with, that shape shows up:
- Revenue per customer (most customers small, a handful of whales)
- Time-on-page (most sessions short, some marathon power-users)
- LTV (most users churn early, some stay for years)
- Salary in a hiring funnel (most candidates in a band, a few outliers at the top)
- Order value, page load latency, support-ticket-per-customer counts — same shape
In every case, the mean tells the optimistic story. Executive decks love it because it makes the business look bigger than it is. “Our average customer spends $58” sounds healthier than “half our customers spend less than $36.” Both numbers are true. They suggest different products.
The median is the safer default for decisions that depend on what the typical unit looks like — pricing thresholds, capacity planning, policy targets, anything where a wrong answer means the median customer doesn’t show up. Use the mean when you care about the total: total revenue, total impressions, total server load, anything where the whale customers contribute proportionally to business outcomes. Use the median when you’re making a threshold decision, a capacity decision, or a pricing decision that has to work for the majority of units.
A useful sanity check before any single-number summary lands in a deck: plot the histogram. If the distribution is symmetric, mean and median agree and either is fine. If it’s skewed, the gap between them tells you which decisions are at risk.
Computing both in SQL
Every warehouse has built-in functions for mean and median. The mean is easy: AVG(). The median is one of two patterns depending on your dialect.
Postgres, Snowflake, BigQuery, Redshift:
SELECT
AVG(session_duration_sec) AS mean_duration,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY session_duration_sec) AS median_duration,
ROUND(100.0 * (AVG(session_duration_sec) -
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY session_duration_sec))
/ PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY session_duration_sec), 1) AS skew_pct
FROM events.sessions
WHERE session_date >= CURRENT_DATE - INTERVAL '30 days';
MySQL 8, ClickHouse: ClickHouse uses quantile(0.5)(session_duration_sec). MySQL 8 has no built-in median; the standard workarounds are a window-function trick (ROW_NUMBER ordered, pick the middle) or a UDF.
On a real session-duration dataset, typical output looks like: mean 87 seconds, median 42 seconds, skew 107%. Mean overstates by 2x because a small number of long sessions (idle tabs, scroll-and-leave) drag the average up while never affecting the typical user. The skew_pct column is the line to watch: above 20%, the choice of summary changes the story and the dashboard should be explicit about which one it is using.
Takeaway
Mean and median answer different questions. Mean answers “what’s the total, divided across units.” Median answers “what does the middle unit look like.” On the kind of skewed data that’s standard in operations and analytics, picking the wrong one moves business decisions toward a customer who doesn’t exist. Pick median when you’re sizing for the typical case, mean when you’re sizing for total volume. And before either one lands in a recommendation, look at the histogram.