Average Watch Time by Format

A medium SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.

Domain
SQL
Difficulty
medium
Seniority
L3

Problem

An analyst found that the format comparison sheet averages watch time per title first, so a piece watched once counts as much as one watched fourteen times. Rebuild it from content_views, taking each view's format from content_items, so every view weighs the same in its content type's average watch time, with the content types listed alphabetically.

Which content format keeps viewers watching the longest

Tables

content_items

Columns: content_id INTEGER, title TEXT, content_type TEXT, duration_seconds INTEGER, creator_id INTEGER, publish_date TEXT

content_items: 10 of 200 rows
content_idtitlecontent_typeduration_secondscreator_idpublish_date
243The Ultimate Guide to AI TrendsarticleNULL1972026-02-02
286Top 10 Mobile Appspodcast1542942025-03-03
329Understanding Machine Learning [DRAFT]short2013912026-04-04
372Mastering Tech Startupslivestream2484882025-05-05
415Introduction to Cybersecurityvideo2955852026-06-06
458Deep Dive: Cloud ComputingarticleNULL6822025-07-07
501Behind the Scenes of Product Managementpodcast3897792026-08-08
544Breaking Down DevOpsshort4368762025-09-09
587Exploring Web Developmentlivestream483NULL2026-01-10
630How to Data Sciencevideo53010702025-11-11

content_views

Columns: view_id INTEGER, content_id INTEGER, user_id INTEGER, device_id INTEGER, viewed_at TEXT, watch_seconds INTEGER

content_views: 10 of 200 rows
view_idcontent_iduser_iddevice_idviewed_atwatch_seconds
705337213614042026-02-02 01:07:0041
710650126226072026-03-03 02:14:0072
715963038838102026-04-04 03:21:00103
7212759514410132026-05-05 04:28:00134
7265888640512162026-06-06 05:35:00165
73181017766614192026-07-07 06:42:00196
73711146NULL16222026-08-08 07:49:00227
7424127548818252026-09-09 08:56:00258
747714041749NULL2026-01-10 09:03:00289
75301533301022312026-02-11 10:10:00320

Expected output

First 5 of 5 rows
content_typeavg_watch_time
article1524.1052631578948
livestream1294.6976744186047
podcast1745
short1618.6486486486488
video1730.942857142857

Worked solution and explanation

This is a view-weighted mean across a join, dressed up as a format review. content_type lives only on content_items and watch_seconds only on content_views, so no single table holds both the key and the measure: the join on content_id is the price of entry. Anyone writes the join. What separates candidates is the grain of the average. It has to be taken over view rows after the join, not over per-title averages, and its denominator may count only views that logged a watch time. On this seed one piece has 14 views while most have one, so averaging per title first lets a single view weigh as much as fourteen. Dividing SUM(watch_seconds) by COUNT(*) counts the 16 views with no watch time and drags the formats holding them down.

Walk it

Step 1: Bring the format onto each view

content_views holds one row per view with its content_id and watch_seconds, and the format is content_type on content_items. Join the two on content_id so each view row carries its format. content_id is unique in content_items, so the join attaches exactly one format to each view and never duplicates one.

Step 2: Average at the view grain

Group the joined rows by ci.content_type and take AVG(cv.watch_seconds) directly. Each view row contributes once, so a title with 14 views pulls its format 14 times as hard as a title with one. That is exactly what 'every view weighs the same' asks for.

Step 3: Let AVG handle the missing watch times

16 views have watch_seconds NULL: the view happened, but no watch time was logged. AVG drops them from the numerator and the denominator alike, so you need no COALESCE and no filter. Substituting 0 would claim those viewers watched nothing.

Step 4: List the formats alphabetically

Sort by ci.content_type, which the query returns. The order follows the format name, not the average.

Join, then average every view once
SELECT
    ci.content_type,
    AVG(cv.watch_seconds) AS avg_watch_time
FROM content_items ci
INNER JOIN content_views cv ON ci.content_id = cv.content_id
GROUP BY ci.content_type
ORDER BY ci.content_type
Average of per-title averages
Average watch_seconds per content_id in a CTE, then average those per content_type. A title watched once counts as much as one watched 14 times, so the format's number follows its catalogue, not its audience.
Average over views
Join first and take AVG(cv.watch_seconds) per content_type over the view rows. Each view counts once, which is the figure the sheet was supposed to show.

Common follow-up questions