Find Zombie Sessions solution and explanation
The problem
Table: app_events
+------------------+----------+ | Column Name | Type | +------------------+----------+ | event_id | int | | user_id | int | | event_timestamp | datetime | | event_type | varchar | | session_id | varchar | | event_value | int | +------------------+----------+ event_id is the unique identifier for this table. event_type can be app_open, click, scroll, purchase, or app_close. session_id groups events within the same user session. event_value represents: for purchase - amount in dollars, for scroll - pixels scrolled, for others - NULL.
Write a solution to identify zombie sessions, sessions where users appear active but show abnormal behavior patterns. A session is considered a zombie session if it meets ALL the following criteria:
- The session duration is more than
30minutes. - Has at least
5scroll events. - The click-to-scroll ratio is less than
0.20. - No purchases were made during the session.
Return the result table ordered by scroll_count in descending order, then by session_id in ascending order.
The result format is in the following example.
Example:
Input:
app_events table:
+----------+---------+---------------------+------------+------------+-------------+ | event_id | user_id | event_timestamp | event_type | session_id | event_value | +----------+---------+---------------------+------------+------------+-------------+ | 1 | 201 | 2024-03-01 10:00:00 | app_open | S001 | NULL | | 2 | 201 | 2024-03-01 10:05:00 | scroll | S001 | 500 | | 3 | 201 | 2024-03-01 10:10:00 | scroll | S001 | 750 | | 4 | 201 | 2024-03-01 10:15:00 | scroll | S001 | 600 | | 5 | 201 | 2024-03-01 10:20:00 | scroll | S001 | 800 | | 6 | 201 | 2024-03-01 10:25:00 | scroll | S001 | 550 | | 7 | 201 | 2024-03-01 10:30:00 | scroll | S001 | 900 | | 8 | 201 | 2024-03-01 10:35:00 | app_close | S001 | NULL | | 9 | 202 | 2024-03-01 11:00:00 | app_open | S002 | NULL | | 10 | 202 | 2024-03-01 11:02:00 | click | S002 | NULL | | 11 | 202 | 2024-03-01 11:05:00 | scroll | S002 | 400 | | 12 | 202 | 2024-03-01 11:08:00 | click | S002 | NULL | | 13 | 202 | 2024-03-01 11:10:00 | scroll | S002 | 350 | | 14 | 202 | 2024-03-01 11:15:00 | purchase | S002 | 50 | | 15 | 202 | 2024-03-01 11:20:00 | app_close | S002 | NULL | | 16 | 203 | 2024-03-01 12:00:00 | app_open | S003 | NULL | | 17 | 203 | 2024-03-01 12:10:00 | scroll | S003 | 1000 | | 18 | 203 | 2024-03-01 12:20:00 | scroll | S003 | 1200 | | 19 | 203 | 2024-03-01 12:25:00 | click | S003 | NULL | | 20 | 203 | 2024-03-01 12:30:00 | scroll | S003 | 800 | | 21 | 203 | 2024-03-01 12:40:00 | scroll | S003 | 900 | | 22 | 203 | 2024-03-01 12:50:00 | scroll | S003 | 1100 | | 23 | 203 | 2024-03-01 13:00:00 | app_close | S003 | NULL | | 24 | 204 | 2024-03-01 14:00:00 | app_open | S004 | NULL | | 25 | 204 | 2024-03-01 14:05:00 | scroll | S004 | 600 | | 26 | 204 | 2024-03-01 14:08:00 | scroll | S004 | 700 | | 27 | 204 | 2024-03-01 14:10:00 | click | S004 | NULL | | 28 | 204 | 2024-03-01 14:12:00 | app_close | S004 | NULL | +----------+---------+---------------------+------------+------------+-------------+
Output:
+------------+---------+--------------------------+--------------+ | session_id | user_id | session_duration_minutes | scroll_count | +------------+---------+--------------------------+--------------+ | S001 | 201 | 35 | 6 | +------------+---------+--------------------------+--------------+
Explanation:
- Session S001 (User 201):
- Duration: 10:00:00 to 10:35:00 = 35 minutes (more than 30)
- Scroll events: 6 (at least 5)
- Click events: 0
- Click-to-scroll ratio: 0/6 = 0.00 (less than 0.20)
- Purchases: 0 (no purchases)
- S001 is a zombie session (meets all criteria)
- Session S002 (User 202):
- Duration: 11:00:00 to 11:20:00 = 20 minutes (less than 30)
- Has a purchase event
- S002 is not a zombie session
- Session S003 (User 203):
- Duration: 12:00:00 to 13:00:00 = 60 minutes (more than 30)
- Scroll events: 5 (at least 5)
- Click events: 1
- Click-to-scroll ratio: 1/5 = 0.20 (not less than 0.20)
- Purchases: 0 (no purchases)
- S003 is not a zombie session (click-to-scroll ratio equals 0.20, needs to be less)
- Session S004 (User 204):
- Duration: 14:00:00 to 14:12:00 = 12 minutes (less than 30)
- Scroll events: 2 (less than 5)
- S004 is not a zombie session
The result table is ordered by scroll_count in descending order, then by session_id in ascending order.
Approach overview
Problem Overview: You have a table of user sessions. Find every session that matches the zombie criteria in the problem statement, and return them in the required output format. The core challenge is evaluating per-user session behavior, not just filtering rows one at a time.
Approach 1: Correlated Subquery or Self-Join (O(n²) time, O(1) extra space)
For each session, run a subquery against the same table to check whether that user has other sessions that affect the zombie condition. The key insight is that each row is judged against the full history of its user. This works, but the database re-scans the table for every row. Use this only to confirm the logic on small inputs or while you're still working out the condition.
Approach 2: Grouped Aggregation (O(n) time, O(users) space)
Group sessions by user_id and compute the aggregates the zombie condition needs, such as counts, min and max timestamps, or sums of durations. Then filter the groups with a HAVING clause in MySQL, or with boolean masks after groupby().agg() in Pandas. You scan the table once and keep one small accumulator per user. Prefer this approach in every real query, because the database or DataFrame engine handles the hash-based grouping for you. Joining the grouped result back to the sessions table recovers the matching rows when the output needs session-level detail.
Recommended for interviews: Start with the correlated subquery to show you understand the zombie condition. Then move to grouped aggregation and explain why one pass with per-key accumulators beats repeated scans. The brute force shows understanding, but the grouped version shows skill. Mention database and hash table grouping as the mechanism behind the optimal solution, and link to SQL practice for more window and aggregate patterns.
Solution
Thinking
A zombie session is defined by duration, scroll count, click-to-scroll ratio, and the absence of a purchase. Aggregate by
(session_id,user_id).Duration is the minute difference between the last and first timestamps; the three event types are counted separately.
Keep sessions of at least
30minutes, at least five scrolls, a click ratio below0.2, and no purchase, ordered by scrolls descending then session id.
We can group the sessions by session_id, calculate the session duration, the number of scroll events, click events, and purchase events for each session, then filter according to the conditions given in the problem. Finally, we sort by the number of scroll events in descending order and by session ID in ascending order.
# Write your MySQL query statement below
SELECT
session_id,
user_id,
TIMESTAMPDIFF(MINUTE, MIN(event_timestamp), MAX(event_timestamp)) session_duration_minutes,
SUM(event_type = 'scroll') scroll_count
FROM app_events
GROUP BY session_id
HAVING
session_duration_minutes >= 30
AND SUM(event_type = 'click') / SUM(event_type = 'scroll') < 0.2
AND SUM(event_type = 'purchase') = 0
AND SUM(event_type = 'scroll') >= 5
ORDER BY scroll_count DESC, session_id;import pandas as pd
def find_zombie_sessions(app_events: pd.DataFrame) -> pd.DataFrame:
if not pd.api.types.is_datetime64_any_dtype(app_events["event_timestamp"]):
app_events["event_timestamp"] = pd.to_datetime(app_events["event_timestamp"])
grouped = app_events.groupby(["session_id", "user_id"])
result = grouped.agg(
session_duration_minutes=(
"event_timestamp",
lambda x: (x.max() - x.min()).total_seconds() // 60,
),
scroll_count=("event_type", lambda x: (x == "scroll").sum()),
click_count=("event_type", lambda x: (x == "click").sum()),
purchase_count=("event_type", lambda x: (x == "purchase").sum()),
).reset_index()
result = result[
(result["session_duration_minutes"] >= 30)
& (result["click_count"] / result["scroll_count"] < 0.2)
& (result["purchase_count"] == 0)
& (result["scroll_count"] >= 5)
]
result = result.sort_values(
by=["scroll_count", "session_id"], ascending=[False, True]
).reset_index(drop=True)
return result[["session_id", "user_id", "session_duration_minutes", "scroll_count"]]Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Correlated Subquery / Self-Join | O(n²) | O(1) extra | Small tables, verifying logic |
| Grouped Aggregation (MySQL or Pandas) | O(n) | O(users) | General case, production queries, interviews |
Frequently asked questions
Find Zombie Sessions Python solution?
In Python, load the sessions into a Pandas DataFrame, call groupby on user_id with agg for the needed metrics, then filter the resulting DataFrame with a boolean condition. Merge the matching user_ids back to the sessions frame if you need session-level rows. The whole solution is one pass over the data.
Is Find Zombie Sessions easy or hard?
Find Zombie Sessions is rated Hard, with a 51.6% acceptance rate. The difficulty comes from translating the zombie criteria into the correct aggregate conditions and avoiding the slow correlated subquery pattern. Once you see the grouping, the implementation is short.
How to solve Find Zombie Sessions in O(n)?
Use a single GROUP BY on user_id and compute the required aggregates in one pass. Store one accumulator per user, then filter groups with HAVING or a mask. Avoid per-row subqueries, since they force repeated scans of the same table.
What is the best approach for Find Zombie Sessions?
The best approach is grouped aggregation. Group the sessions by user, compute the per-user metrics the zombie condition depends on, and filter the groups with HAVING in MySQL or a boolean mask in Pandas. This runs in O(n) time with O(users) extra space.
What data structure is used in Find Zombie Sessions?
The solution relies on hash-based grouping. The database or DataFrame engine keeps a hash map from each user_id to its running aggregates, which gives O(1) average lookup per row. No explicit custom data structure is needed.
What is the time complexity of Find Zombie Sessions?
The optimal grouped aggregation solution runs in O(n) time, where n is the number of session rows, because it scans the table once. The correlated subquery approach runs in O(n²) time because it re-scans related rows for each session.
Is Find Zombie Sessions asked at Google, Amazon, or Meta?
Find Zombie Sessions is a SQL-style problem that tests grouped aggregation and filtering logic, a pattern common in data-focused interviews. Check company-specific question trackers for exact frequency, since FleetCode does not publish per-company tags for this problem.
