Session Length
Session length is the duration assigned to a visit by a measurement policy. Many event-based tools measure the interval between the first and last recorded events; others collect foreground or engagement time. The actual departure time is often unobserved, so recorded duration is not automatically time spent actively using the product.
\[ \text{session length} = \text{session end} - \text{session start}, \qquad \text{average} = \frac{\text{total time across sessions}}{\text{number of sessions}} \]
Analytics tools compute it automatically. A news app opened at 8:00 and closed at 8:12 gives 12 minutes only if those boundaries are observed and counted by the measurement policy; 2,500 minutes across 1,000 sessions in a week gives an average of 2.5 minutes; a 45-minute gaming session suggests deeper engagement and, in that category, a higher chance of in-game spending.
It is read as an engagement indicator, a retention signal, a driver of ad revenue where impressions scale with time, and a prompt for design work. Each of those readings depends on a definition that the tool made on your behalf.
Four things the average hides
The timeout defines the session
One user produces sixteen events, given here as minutes since the first event. A new session starts whenever the gap between two consecutive events is longer than the inactivity timeout, and a session’s length is the time from its first to its last event. The code splits the same events with five different timeouts.
import numpy as np
events = np.array([0, 2, 5, 9, 12, 20, 28, 48, 51, 55, 75, 120, 124, 170, 175, 240])
gaps = np.diff(events)
for timeout in (5, 15, 30, 60, 120):
breaks = np.where(gaps > timeout)[0] # a gap above the timeout ends a session
starts = np.r_[0, breaks + 1]
ends = np.r_[breaks, len(events) - 1]
lengths = events[ends] - events[starts]
unit = "session" if len(lengths) == 1 else "sessions"
print(f"timeout {timeout:>3} min: {len(lengths)} {unit}, average {lengths.mean():.1f} min, "
f"total time counted {lengths.sum()} min")
timeout 5 min: 8 sessions, average 3.5 min, total time counted 28 min
timeout 15 min: 6 sessions, average 7.3 min, total time counted 44 min
timeout 30 min: 4 sessions, average 21.0 min, total time counted 84 min
timeout 60 min: 2 sessions, average 87.5 min, total time counted 175 min
timeout 120 min: 1 session, average 240.0 min, total time counted 240 min
The inactivity timeout helps define sessions, together with event collection and duration rules. One user’s sixteen events are eight sessions averaging 3.5 minutes at a five-minute timeout and a single 240-minute session at two hours, and the total time counted goes from 28 minutes to 240. Comparing session length across tools, or across a period when the timeout changed, compares settings. The thirty-minute default that most analytics products use is a convention inherited from early web analytics rather than a property of user behavior.
Skew: the mean describes few sessions
Now take 400,000 simulated sessions whose lengths follow a lognormal distribution with shape 1.3 and scale 2.2 minutes. The code reports the mean the dashboard would show next to the median, the 90th percentile, the share of all time contributed by the longest tenth of sessions, and the share of sessions shorter than the mean.
from scipy.stats import lognorm
rng = np.random.default_rng(0)
n = 400_000
lengths = lognorm(s=1.3, scale=2.2).ppf(rng.random(n))
p90 = np.quantile(lengths, 0.90)
print(f"mean (the reported number): {lengths.mean():.2f} minutes")
print(f"median: {np.median(lengths):.2f} minutes")
print(f"90th percentile: {p90:.2f} minutes")
print(f"share of time from the top 10%: {lengths[lengths >= p90].sum() / lengths.sum():.1%}")
print(f"sessions below the mean: {np.mean(lengths < lengths.mean()):.1%}")
mean (the reported number): 5.14 minutes
median: 2.19 minutes
90th percentile: 11.62 minutes
share of time from the top 10%: 50.9%
sessions below the mean: 74.3%
74.3% of sessions are shorter than the average and the longest tenth supplies 50.9% of all time spent, so the mean is a number very few sessions resemble. A moving average can be the whole base shifting or a handful of heavy users changing habits, and the two call for opposite responses. Reporting a median and a top-decile share next to the mean separates them.
Whose average: sessions or users
Eight users have different numbers of sessions: 1, 1, 1, 2, 2, 3, 8 and 20, with an average session length of 3, 3, 3, 4, 4, 5, 12 and 25 minutes respectively. The per-session average weights each user by the number of sessions; the per-user average weights every user equally.
sessions_per_user = np.array([1, 1, 1, 2, 2, 3, 8, 20])
average_length = np.array([3.0, 3.0, 3.0, 4.0, 4.0, 5.0, 12.0, 25.0]) # minutes per session
per_session = np.sum(sessions_per_user * average_length) / sessions_per_user.sum()
per_user = average_length.mean()
print(f"average per session: {per_session:.2f} minutes")
print(f"average per user: {per_user:.2f} minutes")
average per session: 16.74 minutes
average per user: 7.38 minutes
Averaging over sessions gives 16.74 minutes and averaging over users gives 7.38, because the heaviest users contribute the most sessions and therefore the most weight. Both are legitimate and they answer different questions — “how long is a typical visit” against “how long does a typical user stay” — and a dashboard rarely says which is shown.
A shorter session is not a worse one
Finally, a hypothetical before-and-after comparison of a checkout flow, with 100,000 sessions in each period. The completion rate is completed sessions divided by sessions, and revenue is completed sessions times \$45.
| Period | Sessions | Average length (minutes) | Completed | Completion rate | Revenue |
|---|---|---|---|---|---|
| Before | 100,000 | 6.4 | 18,000 | 18.0% | 810,000 |
| After | 100,000 | 3.9 | 26,500 | 26.5% | 1,192,500 |
Average session length falls by 39% while revenue rises by 47%. Those chosen inputs show that shorter sessions can accompany better completion and revenue; they do not establish that a redesign caused the change or that earlier users were stuck. On a task-oriented surface a falling session length is the goal; on a content surface it is a warning. The metric has no direction of its own, which means it cannot be a target without a companion metric that does.
Using it well
Its real uses are comparative rather than absolute. Benchmark it before and after a feature launch, holding timeout, event instrumentation, foreground/idle handling and duration rules fixed. Segment it — premium against free, new against returning — since the comparison controls for most of what makes the level uninterpretable. Use it in funnels to find where short sessions come from early drop-out rather than quick success. And follow it by cohort, which is where a change in behavior shows up cleanly.
The remaining caveats are the usual ones and they survive the measurements. Ideal length is product-specific — a few minutes for a news site, half an hour or more for video. Idle time inflates it when a tab sits open, which is why some tools cap a session or require an event to extend it. And “longer” is only better where time itself is the product.
Three habits follow. Publish the timeout alongside the number. Report the median and the per-user average next to the per-session mean. And pair it with an outcome metric — task completion, conversion rate, retention — so that a movement has a direction.
References
- SciPy documentation,
scipy.stats.lognorm. docs.scipy.org
Discover more from Insightful Data Lab
Subscribe to get the latest posts sent to your email.
