Hook: A distinct count is the SQL version of asking, “How many unique people walked in?” instead of “How many footsteps were heard?” Interviewers love it because it tests whether you understand duplicates and NULL, not just syntax.
Question: What does COUNT(DISTINCT ...) do in SQL?
Answer: It counts the number of unique non-NULL values in a column. Duplicate values are treated as one, so repeated rows do not increase the count. If you need distinct combinations of columns, many databases require a subquery or a dialect-specific form.
Interview-Ready Answer: I use COUNT(DISTINCT column) when I need the number of unique values, not the number of rows. It ignores duplicates and also ignores NULL, so the result is often smaller than COUNT(*). If I need distinct pairs or more complex grouping, I usually switch to a SELECT DISTINCT ... subquery so the logic stays portable across databases.
Detailed Explanation: A distinct count answers: “How many different values exist here?” The database must first look at each row, keep only the values that are not NULL, remove duplicates, and then count what is left. A good mental model is a bouncer at a club: every guest can enter the list once, but repeat arrivals do not get a second wristband.
COUNT(DISTINCT ...). That expression can be a plain column or a computed value like LOWER(email).NULL values because COUNT only counts non-NULL inputs.| Form | Counts | NULLs | Typical use |
|---|---|---|---|
COUNT(*) | All rows | Included | Total row count |
COUNT(col) | Non-NULL rows | Ignored | Filled values only |
COUNT(DISTINCT col) | Unique non-NULL values | Ignored | Unique people, products, cities |
Distinct counts usually cost more than plain counts because the engine must remember what it has already seen. In a large table with 10 million rows and 200,000 unique values, the engine may keep a large in-memory structure; if memory is too small, it can spill to disk, which is slower. In simple terms, COUNT(*) is usually cheapest, COUNT(col) is close behind, and COUNT(DISTINCT col) is the one that may need real work.
As a rough mental model, the time is usually O(n) on average for hash-based aggregation and can look more like O(n log n) when sorting is needed. Space is proportional to the number of unique values, often written as O(k), where k is the distinct cardinality. That is why a column with 50 unique values is easy, while a column with millions of unique values can be expensive.
NULL values are ignored, so a column with only NULLs returns 0.'A' and 'a' may be treated as the same; in others, they are different.COUNT(DISTINCT LOWER(email)).SELECT DISTINCT is often the safest answer.Memory Hook: “One wristband per guest.” If you have already counted that person, the bouncer does not hand out another wristband just because they came back in line.
Real-World Story: Imagine a checkout service for an e-commerce site. The product team wants to know how many unique customers placed an order today, not how many order rows were written. One customer may retry payment, and one order can generate multiple events, so a plain row count would overstate demand. The correct query uses COUNT(DISTINCT customer_id) so the dashboard reflects real people, not repeated attempts.
What goes wrong when someone misunderstands this? The analytics dashboard suddenly shows a huge spike in customers after a payment incident, even though traffic did not increase. Logs reveal repeated inserts for the same customer_id, support tickets mention “double counted sales,” and the business team starts making bad staffing or inventory decisions. The symptom is not a crash; it is a misleading number that quietly breaks trust.
-- Demonstrates distinct counting, duplicate collapse, NULL handling, and a safe multi-column pattern.
-- This is standard SQL and should run in common relational databases.
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS only_nulls;
CREATE TABLE orders (
order_id INTEGER,
customer_id INTEGER,
country VARCHAR(20),
status VARCHAR(20)
);
INSERT INTO orders (order_id, customer_id, country, status) VALUES
(1, 101, 'US', 'paid'),
(2, 101, 'US', 'paid'), -- duplicate customer_id; should count once in COUNT(DISTINCT customer_id)
(3, 102, 'US', 'paid'),
(4, 103, 'CA', 'refunded'),
(5, NULL, 'CA', 'paid'), -- NULL is ignored by COUNT(DISTINCT ...)
(6, 104, 'CA', 'paid'),
(7, 104, 'CA', 'paid');
-- Compare the three common count forms.
SELECT
COUNT(*) AS total_rows, -- every row
COUNT(customer_id) AS non_null_customer_rows, -- rows where customer_id is not NULL
COUNT(DISTINCT customer_id) AS distinct_customers,
COUNT(DISTINCT country) AS distinct_countries
FROM orders;
-- Edge case: a column containing only NULLs returns 0 for COUNT(DISTINCT ...).
CREATE TABLE only_nulls (
value INTEGER
);
INSERT INTO only_nulls (value) VALUES
(NULL),
(NULL),
(NULL);
SELECT
COUNT(DISTINCT value) AS distinct_from_all_nulls
FROM only_nulls;
-- Portable pattern for counting distinct combinations of multiple columns.
-- Some databases have special syntax for this, but the subquery works broadly.
SELECT
COUNT(*) AS distinct_customer_country_pairs
FROM (
SELECT DISTINCT customer_id, country
FROM orders
) AS pairs;Follow-up & Tricky Questions:
SELECT COUNT(*) FROM (SELECT DISTINCT col1, col2 FROM t) x. That makes the deduplication step explicit and easy to explain.COUNT(DISTINCT ...) slower than COUNT(*)? Because the engine must track uniqueness, not just increment a counter. That means extra memory, and sometimes a sort or a spill to disk if the distinct set is large.NULLs? The result is 0, because COUNT ignores NULL values entirely. This is a very common interview trap.COUNT(DISTINCT col) the same as SELECT DISTINCT col? No. SELECT DISTINCT returns the unique values themselves, while COUNT(DISTINCT ...) returns just the number of those unique values.LOWER(email)? Yes, and that is often the right move when you want normalized uniqueness. You are counting the distinct result of the expression, not the raw stored text.'A' and 'a' may collapse to one value; in case-sensitive collations, they may count separately.COUNT(DISTINCT 1) always return 1? If the query has at least one row, yes, because the expression is the same non-NULL constant for every row. If there are no rows, the result is 0.Tricky / gotcha questions:
COUNT(DISTINCT col) count one NULL as a unique value? No, NULL is excluded from the count entirely.COUNT(DISTINCT TRIM(LOWER(name))) can collapse values that look different in storage but equal after normalization.Common Mistakes:
COUNT(*) when the question asks for unique values. COUNT(*) counts rows, not distinct entities; switch to COUNT(DISTINCT ...) when duplicates exist.NULL is ignored. If the answer seems too small, check whether missing values should be handled with COALESCE or a different rule.SELECT DISTINCT subquery.Alice@example.com and alice@example.com are the same, count the normalized form.Memory Hook: “One wristband per guest.” Duplicate arrivals do not get extra wristbands, and NULL never gets into the club.
Cheat Sheet:
COUNT(DISTINCT col) = unique non-NULL values.COUNT(*) = every row.COUNT(col) = non-NULL rows.SELECT DISTINCT subquery is the safest portable pattern.Practice Tasks:
COUNT(*) and COUNT(DISTINCT product_id).NULL values and confirm that the distinct count does not include them.SELECT DISTINCT subquery for two columns and compare its result to the single-column count.