<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gowtham Potureddi</title>
    <description>The latest articles on DEV Community by Gowtham Potureddi (@gowthampotureddi).</description>
    <link>https://dev.to/gowthampotureddi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3874592%2Fb901f929-0a60-4dd2-9dac-22ce22291bdc.png</url>
      <title>DEV Community: Gowtham Potureddi</title>
      <link>https://dev.to/gowthampotureddi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gowthampotureddi"/>
    <language>en</language>
    <item>
      <title>Meta Data Engineering Interview Questions: Top Topics, Problems &amp; Solutions</title>
      <dc:creator>Gowtham Potureddi</dc:creator>
      <pubDate>Tue, 21 Apr 2026 11:05:14 +0000</pubDate>
      <link>https://dev.to/gowthampotureddi/meta-data-engineering-interview-questions-top-topics-problems-solutions-304g</link>
      <guid>https://dev.to/gowthampotureddi/meta-data-engineering-interview-questions-top-topics-problems-solutions-304g</guid>
      <description>&lt;p&gt;&lt;strong&gt;Meta data engineering&lt;/strong&gt; interviews (and similar big-tech loops) usually lean heavily on &lt;strong&gt;SQL&lt;/strong&gt;—often PostgreSQL-style—and on &lt;strong&gt;Python&lt;/strong&gt; for problems involving hash maps, counting, arrays, strings, and streaming or interval-style logic. Interviewers care that you can write correct queries, pick the right grain, and explain trade-offs—not just memorize syntax.&lt;/p&gt;

&lt;p&gt;This guide introduces the &lt;strong&gt;topics that show up most often&lt;/strong&gt; in that kind of prep: aggregations, joins, windows, dates, null-safe reporting, set logic, and representative Python patterns. The &lt;strong&gt;sample questions here are original teaching examples&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe9wey9gb649b1as9yqfo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe9wey9gb649b1as9yqfo.png" alt="Header graphic for Meta data engineering interview prep with SQL and Python themes on a dark gradient." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Top Meta Data Engineering Interview Topics
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Why it matters on Meta-style sets&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Aggregation &amp;amp; &lt;code&gt;GROUP BY&lt;/code&gt; / &lt;code&gt;HAVING&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Summaries by brand, seller, day, etc.; filter groups after summing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Filtering (&lt;code&gt;WHERE&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Keep the right rows before you aggregate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Joins &amp;amp; deduplication&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Combine tables without inflating row counts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Window functions &amp;amp; ranking&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Running totals, top-N per group, ties.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Subqueries &amp;amp; CTEs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multi-step logic readable in one script.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Dates &amp;amp; time-series&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Daily revenue, latency, cohort-style buckets.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt; &amp;amp; safe percentages&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Correct numerators/denominators; avoid silent wrong rates.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Set-style logic (overlap / both / except)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Customers in both stores, A-not-B, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Python: hash maps &amp;amp; counting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dicts, &lt;code&gt;Counter&lt;/code&gt;, frequency and “most common.”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Python: streaming &amp;amp; intervals&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Update state as events arrive; merge intervals.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If you are new to SQL:&lt;/strong&gt; In most databases the engine processes a query roughly in this order: &lt;strong&gt;&lt;code&gt;FROM&lt;/code&gt; / joins → &lt;code&gt;WHERE&lt;/code&gt; (filter rows) → &lt;code&gt;GROUP BY&lt;/code&gt; → compute aggregates → &lt;code&gt;HAVING&lt;/code&gt; (filter groups) → window functions → &lt;code&gt;SELECT&lt;/code&gt; / &lt;code&gt;ORDER BY&lt;/code&gt;&lt;/strong&gt;. When in doubt, ask: “Am I filtering &lt;strong&gt;one row at a time&lt;/strong&gt; (&lt;code&gt;WHERE&lt;/code&gt;) or &lt;strong&gt;a whole group after summing&lt;/strong&gt; (&lt;code&gt;HAVING&lt;/code&gt;)?”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. Aggregation and GROUP BY Concepts in Data Engineering
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Aggregation and GROUP BY in SQL for Data Engineering
&lt;/h3&gt;

&lt;p&gt;Picture a table with &lt;strong&gt;many detail rows&lt;/strong&gt;—for example one row per order. &lt;strong&gt;Aggregation&lt;/strong&gt; means: “turn lots of rows into &lt;strong&gt;one summary value&lt;/strong&gt; (or a few values) per &lt;strong&gt;bucket&lt;/strong&gt;.” The bucket is whatever the question cares about: per user, per day, per campaign, and so on.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;GROUP BY&lt;/code&gt;&lt;/strong&gt; defines the &lt;strong&gt;bucket&lt;/strong&gt;: “Put all rows with the same &lt;code&gt;user_id&lt;/code&gt; together,” or “the same &lt;code&gt;(store_id, day)&lt;/code&gt; together.” Every distinct combination of the &lt;code&gt;GROUP BY&lt;/code&gt; columns is one group; the database runs your aggregate functions &lt;strong&gt;separately inside each group&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;SUM(col)&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it does:&lt;/strong&gt; Adds all &lt;strong&gt;numeric&lt;/strong&gt; values of &lt;code&gt;col&lt;/code&gt; in the bucket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt; behavior:&lt;/strong&gt; &lt;code&gt;NULL&lt;/code&gt; cells are &lt;strong&gt;ignored&lt;/strong&gt; (they are not treated as 0). If every value is &lt;code&gt;NULL&lt;/code&gt;, &lt;code&gt;SUM&lt;/code&gt; is usually &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt;, not 0—say that in an interview if the edge case matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typical use:&lt;/strong&gt; Revenue totals, quantities, scores summed per seller or per day.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; In one group, &lt;strong&gt;&lt;code&gt;amount&lt;/code&gt;&lt;/strong&gt; values &lt;strong&gt;10, &lt;code&gt;NULL&lt;/code&gt;, 30&lt;/strong&gt; → &lt;strong&gt;&lt;code&gt;SUM(amount) = 40&lt;/code&gt;&lt;/strong&gt; (only &lt;strong&gt;10 + 30&lt;/strong&gt;).&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;AVG(col)&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it does:&lt;/strong&gt; &lt;strong&gt;Average of non-&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt; values: sum of non-null values divided by &lt;strong&gt;count of non-null values&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt; behavior:&lt;/strong&gt; Rows where &lt;code&gt;col&lt;/code&gt; is &lt;code&gt;NULL&lt;/code&gt; &lt;strong&gt;do not&lt;/strong&gt; enter the numerator or the denominator. If you need “average where missing means 0,” use &lt;strong&gt;&lt;code&gt;AVG(COALESCE(col, 0))&lt;/code&gt;&lt;/strong&gt; (only if the business defines it that way).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typical use:&lt;/strong&gt; Average order value per user, average latency per region.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; Same three rows &lt;strong&gt;10, &lt;code&gt;NULL&lt;/code&gt;, 30&lt;/strong&gt; → &lt;strong&gt;&lt;code&gt;AVG(amount) = 20&lt;/code&gt;&lt;/strong&gt; because &lt;strong&gt;(10 + 30) / 2&lt;/strong&gt;; the &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt; row is not counted in the average.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;COUNT(*)&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it does:&lt;/strong&gt; Counts &lt;strong&gt;how many rows&lt;/strong&gt; are in the bucket—&lt;strong&gt;every row counts&lt;/strong&gt;, even if some columns are &lt;code&gt;NULL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typical use:&lt;/strong&gt; “How many orders per customer?”, “how many events in this hour?”—when each row is one event.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; Same three rows → &lt;strong&gt;&lt;code&gt;COUNT(*) = 3&lt;/code&gt;&lt;/strong&gt; (the &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt; row still counts as a row).&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;COUNT(col)&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it does:&lt;/strong&gt; Counts rows where &lt;strong&gt;&lt;code&gt;col&lt;/code&gt; is not &lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt;. Differs from &lt;strong&gt;&lt;code&gt;COUNT(*)&lt;/code&gt;&lt;/strong&gt; as soon as &lt;code&gt;col&lt;/code&gt; has nulls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Example intuition:&lt;/strong&gt; &lt;code&gt;COUNT(user_id)&lt;/code&gt; might count rows with a known user; &lt;code&gt;COUNT(*)&lt;/code&gt; counts all rows in the group.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Related:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;COUNT(DISTINCT col)&lt;/code&gt;&lt;/strong&gt; counts &lt;strong&gt;unique non-null&lt;/strong&gt; values in the bucket—essential after joins when you must count &lt;strong&gt;people&lt;/strong&gt;, not multiplied rows (see section 3).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; Same three rows → &lt;strong&gt;&lt;code&gt;COUNT(amount) = 2&lt;/code&gt;&lt;/strong&gt;. If the third row were &lt;strong&gt;50&lt;/strong&gt; instead of &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;COUNT(DISTINCT amount)&lt;/code&gt;&lt;/strong&gt; with values &lt;strong&gt;10, 30, 50&lt;/strong&gt; would be &lt;strong&gt;3&lt;/strong&gt;; with &lt;strong&gt;10, 30, 10&lt;/strong&gt; it would be &lt;strong&gt;2&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;MIN(col)&lt;/code&gt; and &lt;code&gt;MAX(col)&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What they do:&lt;/strong&gt; Return the &lt;strong&gt;smallest&lt;/strong&gt; or &lt;strong&gt;largest&lt;/strong&gt; value of &lt;code&gt;col&lt;/code&gt; in the bucket. Works on &lt;strong&gt;orderable&lt;/strong&gt; types: numbers, &lt;strong&gt;dates/timestamps&lt;/strong&gt;, strings (lexicographic order).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt; behavior:&lt;/strong&gt; &lt;code&gt;NULL&lt;/code&gt;s are &lt;strong&gt;skipped&lt;/strong&gt;. If all values are &lt;code&gt;NULL&lt;/code&gt;, the result is &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typical use:&lt;/strong&gt; Latest (&lt;code&gt;MAX(ts)&lt;/code&gt;), earliest (&lt;code&gt;MIN(day)&lt;/code&gt;), cheapest product in a category (&lt;code&gt;MIN(price)&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; Amounts &lt;strong&gt;10, &lt;code&gt;NULL&lt;/code&gt;, 30&lt;/strong&gt; → &lt;strong&gt;&lt;code&gt;MIN(amount) = 10&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;MAX(amount) = 30&lt;/code&gt;&lt;/strong&gt;. For strings &lt;strong&gt;&lt;code&gt;'apple'&lt;/code&gt;, &lt;code&gt;'banana'&lt;/code&gt;&lt;/strong&gt; in one group → &lt;strong&gt;&lt;code&gt;MIN&lt;/code&gt;&lt;/strong&gt; = &lt;strong&gt;&lt;code&gt;'apple'&lt;/code&gt;&lt;/strong&gt; (lexicographic).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worked example — one dataset, several aggregates&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suppose &lt;strong&gt;&lt;code&gt;orders&lt;/code&gt;&lt;/strong&gt; looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;order_id&lt;/th&gt;
&lt;th&gt;user_id&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;td&gt;20.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;103&lt;/td&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;td&gt;40.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;104&lt;/td&gt;
&lt;td&gt;u2&lt;/td&gt;
&lt;td&gt;100.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sum_amt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_amt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;n_rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;n_known_amt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;min_amt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;max_amt&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;user_id&lt;/th&gt;
&lt;th&gt;sum_amt&lt;/th&gt;
&lt;th&gt;avg_amt&lt;/th&gt;
&lt;th&gt;n_rows&lt;/th&gt;
&lt;th&gt;n_known_amt&lt;/th&gt;
&lt;th&gt;min_amt&lt;/th&gt;
&lt;th&gt;max_amt&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;td&gt;60.00&lt;/td&gt;
&lt;td&gt;30.0000…&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;20.00&lt;/td&gt;
&lt;td&gt;40.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;u2&lt;/td&gt;
&lt;td&gt;100.00&lt;/td&gt;
&lt;td&gt;100.0000…&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100.00&lt;/td&gt;
&lt;td&gt;100.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For &lt;strong&gt;u1&lt;/strong&gt;, the row with &lt;strong&gt;&lt;code&gt;amount NULL&lt;/code&gt;&lt;/strong&gt; still counts in &lt;strong&gt;&lt;code&gt;COUNT(*)&lt;/code&gt;&lt;/strong&gt; (3 rows) but not in &lt;strong&gt;&lt;code&gt;SUM&lt;/code&gt; / &lt;code&gt;AVG&lt;/code&gt; / &lt;code&gt;COUNT(amount)&lt;/code&gt;&lt;/strong&gt; (only the 20 and 40 matter). That single picture is how most interviews test whether you understand &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt; with aggregates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;COUNT(DISTINCT)&lt;/code&gt; mini-example:&lt;/strong&gt; if &lt;strong&gt;&lt;code&gt;clicks&lt;/code&gt;&lt;/strong&gt; has two rows for the same &lt;strong&gt;&lt;code&gt;user_id&lt;/code&gt;&lt;/strong&gt; (double click), &lt;strong&gt;&lt;code&gt;COUNT(*)&lt;/code&gt;&lt;/strong&gt; is 2 but &lt;strong&gt;&lt;code&gt;COUNT(DISTINCT user_id)&lt;/code&gt;&lt;/strong&gt; is 1 for that user’s bucket.&lt;/p&gt;

&lt;h4&gt;
  
  
  Conditional aggregation (&lt;code&gt;CASE&lt;/code&gt; inside aggregates)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Idea:&lt;/strong&gt; Count or sum &lt;strong&gt;only some rows in the group&lt;/strong&gt; without splitting into multiple queries—put the condition &lt;strong&gt;inside&lt;/strong&gt; the aggregate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Patterns:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;SUM(CASE WHEN condition THEN col ELSE 0 END)&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;COUNT(CASE WHEN … THEN 1 END)&lt;/code&gt;&lt;/strong&gt; (or &lt;strong&gt;&lt;code&gt;SUM(CASE WHEN … THEN 1 ELSE 0 END)&lt;/code&gt;&lt;/strong&gt;), &lt;strong&gt;&lt;code&gt;AVG(CASE WHEN … THEN col END)&lt;/code&gt;&lt;/strong&gt; (averages only non-null branches).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example — events per user by type&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;user_id&lt;/th&gt;
&lt;th&gt;event_type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;td&gt;view&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;td&gt;purchase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;td&gt;view&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;u2&lt;/td&gt;
&lt;td&gt;view&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'purchase'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;purchases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'view'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;user_id&lt;/th&gt;
&lt;th&gt;total_events&lt;/th&gt;
&lt;th&gt;purchases&lt;/th&gt;
&lt;th&gt;views&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;u2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is the same “one &lt;strong&gt;&lt;code&gt;GROUP BY&lt;/code&gt;&lt;/strong&gt;, many metrics” style as &lt;strong&gt;&lt;code&gt;COUNT(*) FILTER (WHERE …)&lt;/code&gt;&lt;/strong&gt; in PostgreSQL—portable warehouses use &lt;strong&gt;&lt;code&gt;CASE&lt;/code&gt;&lt;/strong&gt; heavily.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;GROUP BY&lt;/code&gt; and &lt;code&gt;HAVING&lt;/code&gt; (how they fit together)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;HAVING&lt;/code&gt;&lt;/strong&gt; is &lt;strong&gt;&lt;code&gt;WHERE&lt;/code&gt; for buckets&lt;/strong&gt;: it runs &lt;strong&gt;after&lt;/strong&gt; grouping, so you can filter on &lt;strong&gt;&lt;code&gt;AVG(amount)&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;COUNT(*)&lt;/code&gt;&lt;/strong&gt;, etc. &lt;strong&gt;&lt;code&gt;WHERE&lt;/code&gt;&lt;/strong&gt; runs &lt;strong&gt;before&lt;/strong&gt; grouping and only sees &lt;strong&gt;raw row&lt;/strong&gt; columns—so &lt;strong&gt;&lt;code&gt;WHERE AVG(amount) &amp;gt; 50&lt;/code&gt;&lt;/strong&gt; is invalid: the average does not exist until after &lt;code&gt;GROUP BY&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example — &lt;code&gt;WHERE&lt;/code&gt; vs &lt;code&gt;HAVING&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;order_id&lt;/th&gt;
&lt;th&gt;brand_id&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;WHERE amount &amp;gt; 30&lt;/code&gt;&lt;/strong&gt; drops row 3 &lt;strong&gt;before&lt;/strong&gt; grouping. Then &lt;strong&gt;&lt;code&gt;GROUP BY brand_id&lt;/code&gt;&lt;/strong&gt; with &lt;strong&gt;&lt;code&gt;SUM(amount)&lt;/code&gt;&lt;/strong&gt; gives A =110 (40+70), B = 200.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;HAVING SUM(amount) &amp;gt; 100&lt;/code&gt;&lt;/strong&gt; runs &lt;strong&gt;after&lt;/strong&gt; grouping on the &lt;strong&gt;unfiltered&lt;/strong&gt; table: A’s sum is 130, B’s is 200—both pass; if you needed “brands with &lt;strong&gt;at least 3&lt;/strong&gt; orders and &lt;strong&gt;sum &amp;gt; 100&lt;/strong&gt;,” you would use &lt;strong&gt;&lt;code&gt;HAVING COUNT(*) &amp;gt;= 3 AND SUM(amount) &amp;gt; 100&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example — same &lt;code&gt;orders&lt;/code&gt; as above, show &lt;code&gt;HAVING&lt;/code&gt; output&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;brand_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_amt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;n_orders&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;brand_id&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;brand_id&lt;/th&gt;
&lt;th&gt;total_amt&lt;/th&gt;
&lt;th&gt;n_orders&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;130&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you add &lt;strong&gt;&lt;code&gt;AND COUNT(*) &amp;gt;= 2&lt;/code&gt;&lt;/strong&gt;, only &lt;strong&gt;A&lt;/strong&gt; remains (&lt;strong&gt;B&lt;/strong&gt; has a single order).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; If the condition uses &lt;strong&gt;&lt;code&gt;SUM&lt;/code&gt; / &lt;code&gt;COUNT&lt;/code&gt; / &lt;code&gt;AVG&lt;/code&gt; / …` of the group&lt;/strong&gt;, use &lt;strong&gt;&lt;code&gt;HAVING&lt;/code&gt;&lt;/strong&gt;. If it only uses &lt;strong&gt;this row’s&lt;/strong&gt; columns, use &lt;strong&gt;&lt;code&gt;WHERE&lt;/code&gt;&lt;/strong&gt; (and put it first—it usually makes the query faster too).&lt;/p&gt;

&lt;p&gt;If a column appears in &lt;code&gt;SELECT&lt;/code&gt; and is &lt;strong&gt;not&lt;/strong&gt; inside an aggregate, it must appear in &lt;strong&gt;&lt;code&gt;GROUP BY&lt;/code&gt;&lt;/strong&gt; (in strict SQL). Otherwise the database does not know which row’s value to show for that column inside a bucket.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common beginner mistakes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Putting &lt;code&gt;AVG(amount) &amp;gt; 50&lt;/code&gt; in &lt;code&gt;WHERE&lt;/code&gt; → use &lt;strong&gt;&lt;code&gt;HAVING&lt;/code&gt;&lt;/strong&gt; after &lt;code&gt;GROUP BY&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Forgetting a column in &lt;code&gt;GROUP BY&lt;/code&gt; when it appears in &lt;code&gt;SELECT&lt;/code&gt; without an aggregate → &lt;strong&gt;invalid&lt;/strong&gt; query in strict SQL.&lt;/li&gt;
&lt;li&gt;Answering at the &lt;strong&gt;wrong grain&lt;/strong&gt; (e.g. one row per &lt;code&gt;ad_id&lt;/code&gt; when the question asked per &lt;strong&gt;campaign&lt;/strong&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SQL Interview Question on Aggregation and GROUP BY
&lt;/h3&gt;

&lt;p&gt;Table &lt;code&gt;orders(order_id, user_id, amount)&lt;/code&gt; lists purchases. Return each &lt;code&gt;user_id&lt;/code&gt; whose &lt;strong&gt;average&lt;/strong&gt; &lt;code&gt;amount&lt;/code&gt; is &lt;strong&gt;greater than 50&lt;/strong&gt; and who has &lt;strong&gt;at least 3&lt;/strong&gt; orders.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution Using GROUP BY and HAVING
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`sql&lt;/p&gt;

&lt;p&gt;SELECT user_id,&lt;br&gt;
       AVG(amount) AS avg_amount,&lt;br&gt;
       COUNT(&lt;em&gt;)    AS order_cnt&lt;br&gt;
FROM orders&lt;br&gt;
GROUP BY user_id&lt;br&gt;
HAVING COUNT(&lt;/em&gt;) &amp;gt;= 3&lt;br&gt;
   AND AVG(amount) &amp;gt; 50;&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this works:&lt;/strong&gt; We &lt;strong&gt;group&lt;/strong&gt; all orders by &lt;code&gt;user_id&lt;/code&gt;, compute &lt;strong&gt;average&lt;/strong&gt; and &lt;strong&gt;count&lt;/strong&gt; per user, then &lt;strong&gt;keep only&lt;/strong&gt; groups that pass both conditions. Those conditions depend on aggregates, so they belong in &lt;code&gt;HAVING&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practice&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQL · Topic —&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/topic/aggregation" rel="noopener noreferrer"&gt;Aggregation problems (all companies)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;COMPANY · Meta — aggregation&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/aggregation" rel="noopener noreferrer"&gt;Meta-tagged aggregation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  2. Data Filtering and WHERE Clause in SQL
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Filtering Data Using the WHERE Clause in SQL
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;WHERE&lt;/code&gt;&lt;/strong&gt; is the &lt;strong&gt;row filter&lt;/strong&gt;: after &lt;code&gt;FROM&lt;/code&gt; and joins, each row is tested once. If the condition is true, the row &lt;strong&gt;stays&lt;/strong&gt; and can be grouped, counted, or shown in the result; if false, the row is &lt;strong&gt;dropped&lt;/strong&gt; and never enters aggregates. Rows removed here are &lt;strong&gt;invisible&lt;/strong&gt; to &lt;code&gt;GROUP BY&lt;/code&gt; and to &lt;code&gt;COUNT(*)&lt;/code&gt; on the remaining set—so push the filters that narrow the problem &lt;strong&gt;early&lt;/strong&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  Comparisons (&lt;code&gt;=&lt;/code&gt;, &lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;=&lt;/code&gt;, &lt;code&gt;&amp;gt;=&lt;/code&gt;)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Compare a column to a literal or another column: &lt;strong&gt;&lt;code&gt;amount &amp;gt; 100&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;ts &amp;gt;= TIMESTAMP '2026-01-01'&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strings&lt;/strong&gt; compare in the database’s collation order unless you use explicit functions.&lt;/li&gt;
&lt;li&gt;Watch &lt;strong&gt;types&lt;/strong&gt;: comparing a &lt;strong&gt;&lt;code&gt;DATE&lt;/code&gt;&lt;/strong&gt; to a &lt;strong&gt;&lt;code&gt;TIMESTAMP&lt;/code&gt;&lt;/strong&gt; may require casting so you do not accidentally exclude boundary instants.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;th&gt;status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;paid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;refunded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;paid&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;WHERE amount &amp;gt; 50 AND status &amp;lt;&amp;gt; 'refunded'&lt;/code&gt; keeps rows &lt;strong&gt;1&lt;/strong&gt; and &lt;strong&gt;3&lt;/strong&gt; only.&lt;/p&gt;
&lt;h4&gt;
  
  
  Range and membership
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;BETWEEN a AND b&lt;/code&gt;&lt;/strong&gt; is &lt;strong&gt;inclusive on both ends&lt;/strong&gt;. For timestamps, &lt;strong&gt;&lt;code&gt;&amp;gt;= start AND &amp;lt; end&lt;/code&gt;&lt;/strong&gt; (half-open) is often safer so you do not double-count midnight.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;IN ('US', 'CA', 'UK')&lt;/code&gt;&lt;/strong&gt; is shorthand for multiple &lt;strong&gt;&lt;code&gt;OR&lt;/code&gt;&lt;/strong&gt;s; &lt;strong&gt;&lt;code&gt;NOT IN (...)&lt;/code&gt;&lt;/strong&gt; can surprise you if the list or a subquery contains &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt;—&lt;strong&gt;&lt;code&gt;NOT EXISTS&lt;/code&gt;&lt;/strong&gt; is sometimes safer (see section 8).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;user_id&lt;/th&gt;
&lt;th&gt;country&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;a&lt;/td&gt;
&lt;td&gt;US&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;b&lt;/td&gt;
&lt;td&gt;IN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;c&lt;/td&gt;
&lt;td&gt;CA&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;WHERE country IN ('US', 'CA')&lt;/code&gt; returns users &lt;strong&gt;a&lt;/strong&gt; and &lt;strong&gt;c&lt;/strong&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;LIKE&lt;/code&gt; and text patterns
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;LIKE&lt;/code&gt;&lt;/strong&gt; uses &lt;strong&gt;&lt;code&gt;%&lt;/code&gt;&lt;/strong&gt; (any substring) and &lt;strong&gt;&lt;code&gt;_&lt;/code&gt;&lt;/strong&gt; (single character): &lt;strong&gt;&lt;code&gt;email LIKE '%@company.com'&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Case sensitivity&lt;/strong&gt; depends on the database/column collation; use &lt;strong&gt;&lt;code&gt;ILIKE&lt;/code&gt;&lt;/strong&gt; (PostgreSQL) or &lt;strong&gt;&lt;code&gt;LOWER(col)&lt;/code&gt;&lt;/strong&gt; if the problem requires case-insensitive match.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;email&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ada@work.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bob@gmail.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;WHERE email LIKE '%@work.com'&lt;/code&gt; matches &lt;strong&gt;ada&lt;/strong&gt; only.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;IS NULL&lt;/code&gt; / &lt;code&gt;IS NOT NULL&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt; means unknown&lt;/strong&gt;, not “empty string” or 0. &lt;strong&gt;&lt;code&gt;WHERE col = NULL&lt;/code&gt;&lt;/strong&gt; is wrong in SQL; use &lt;strong&gt;&lt;code&gt;WHERE col IS NULL&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;IS NOT NULL&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;phone&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;555-0100&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;WHERE phone IS NOT NULL&lt;/code&gt; returns row &lt;strong&gt;1&lt;/strong&gt;; &lt;code&gt;WHERE phone IS NULL&lt;/code&gt; returns row &lt;strong&gt;2&lt;/strong&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  Combining conditions
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AND&lt;/code&gt;&lt;/strong&gt; — all must hold. &lt;strong&gt;&lt;code&gt;OR&lt;/code&gt;&lt;/strong&gt; — at least one holds; use &lt;strong&gt;parentheses&lt;/strong&gt; when mixing with &lt;strong&gt;&lt;code&gt;AND&lt;/code&gt;&lt;/strong&gt; so intent is unambiguous: &lt;strong&gt;&lt;code&gt;WHERE (a OR b) AND c&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;region&lt;/th&gt;
&lt;th&gt;is_active&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;EU&lt;/td&gt;
&lt;td&gt;TRUE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;UK&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;US&lt;/td&gt;
&lt;td&gt;TRUE&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;WHERE (region = 'EU' OR region = 'UK') AND is_active = TRUE&lt;/code&gt;&lt;/strong&gt; → row &lt;strong&gt;1&lt;/strong&gt; only (UK row is inactive).&lt;/li&gt;
&lt;li&gt;Without parentheses, &lt;strong&gt;&lt;code&gt;AND&lt;/code&gt; binds tighter than &lt;code&gt;OR&lt;/code&gt;&lt;/strong&gt;, so &lt;strong&gt;&lt;code&gt;WHERE region = 'EU' OR region = 'UK' AND is_active = TRUE&lt;/code&gt;&lt;/strong&gt; is read as &lt;strong&gt;&lt;code&gt;region = 'EU' OR (region = 'UK' AND is_active = TRUE)&lt;/code&gt;&lt;/strong&gt;—not the same as “(EU or UK) and active.” Always parenthesize &lt;strong&gt;&lt;code&gt;OR&lt;/code&gt;&lt;/strong&gt; groups when you mix with &lt;strong&gt;&lt;code&gt;AND&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because &lt;code&gt;WHERE&lt;/code&gt; runs &lt;strong&gt;before&lt;/strong&gt; &lt;code&gt;GROUP BY&lt;/code&gt;, it cannot reference &lt;strong&gt;per-group&lt;/strong&gt; totals. Those belong in &lt;strong&gt;&lt;code&gt;HAVING&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common beginner mistakes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using &lt;code&gt;=&lt;/code&gt; instead of &lt;strong&gt;&lt;code&gt;IS NULL&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Mixing &lt;strong&gt;&lt;code&gt;DATE&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;TIMESTAMP&lt;/code&gt;&lt;/strong&gt; or time zones without casting.&lt;/li&gt;
&lt;li&gt;Putting &lt;strong&gt;aggregate&lt;/strong&gt; conditions in &lt;code&gt;WHERE&lt;/code&gt; (use &lt;strong&gt;&lt;code&gt;HAVING&lt;/code&gt;&lt;/strong&gt; instead).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Data Engineering Interview Question on WHERE and Filtering
&lt;/h3&gt;

&lt;p&gt;From &lt;code&gt;events(user_id, event_type, ts)&lt;/code&gt;, count how many &lt;strong&gt;&lt;code&gt;purchase&lt;/code&gt;&lt;/strong&gt; events happened &lt;strong&gt;after&lt;/strong&gt; &lt;code&gt;2026-01-01&lt;/code&gt; for each &lt;code&gt;user_id&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Solution Using WHERE and GROUP BY
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;sql&lt;br&gt;
SELECT user_id,&lt;br&gt;
       COUNT(*) AS purchase_cnt&lt;br&gt;
FROM events&lt;br&gt;
WHERE event_type = 'purchase'&lt;br&gt;
  AND ts &amp;gt; TIMESTAMP '2026-01-01 00:00:00'&lt;br&gt;
GROUP BY user_id;&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this works:&lt;/strong&gt; &lt;code&gt;WHERE&lt;/code&gt; keeps only &lt;strong&gt;purchase&lt;/strong&gt; rows after the cutoff; then we &lt;strong&gt;count per user&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practice&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQL · Topic —&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/topic/filtering" rel="noopener noreferrer"&gt;Filtering &amp;amp; WHERE clause&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;COMPANY · Meta — filtering&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/filtering" rel="noopener noreferrer"&gt;Meta-tagged filtering&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  3. Joins and Data Deduplication Techniques
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Joins and Deduplication in SQL for Data Engineering
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;join&lt;/strong&gt; answers: “For each row in table A, &lt;strong&gt;which&lt;/strong&gt; rows in table B belong with it?” You declare that with &lt;strong&gt;&lt;code&gt;ON a.key = b.key&lt;/code&gt;&lt;/strong&gt;. Think of it as &lt;strong&gt;enriching&lt;/strong&gt; events (orders, clicks) with reference data (users, ads).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fact vs dimension and grain (how DEs talk about joins)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fact table:&lt;/strong&gt; Many rows, often &lt;strong&gt;one row per event or transaction&lt;/strong&gt; (order line, click, impression)—the thing you &lt;strong&gt;&lt;code&gt;SUM&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;COUNT&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dimension table:&lt;/strong&gt; Fewer rows, &lt;strong&gt;one row per entity&lt;/strong&gt; you describe (user, ad, product)—the thing you &lt;strong&gt;&lt;code&gt;JOIN&lt;/code&gt;&lt;/strong&gt; to get names, categories, brand ids.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grain:&lt;/strong&gt; “One result row per &lt;strong&gt;what&lt;/strong&gt;?” (per order, per user per day, per campaign). Wrong grain after a join usually means &lt;strong&gt;fan-out&lt;/strong&gt; (section below): you aggregated at &lt;strong&gt;order&lt;/strong&gt; level but summed a &lt;strong&gt;user&lt;/strong&gt; attribute.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;order_lines&lt;/code&gt;&lt;/strong&gt; (fact: &lt;strong&gt;&lt;code&gt;order_id&lt;/code&gt;, &lt;code&gt;sku&lt;/code&gt;, &lt;code&gt;qty&lt;/code&gt;&lt;/strong&gt;) joins &lt;strong&gt;&lt;code&gt;products&lt;/code&gt;&lt;/strong&gt; (dimension: &lt;strong&gt;&lt;code&gt;sku&lt;/code&gt;, list_price, name`&lt;/strong&gt;). &lt;strong&gt;&lt;code&gt;SUM(qty)&lt;/code&gt;&lt;/strong&gt; by &lt;strong&gt;&lt;code&gt;sku&lt;/code&gt;&lt;/strong&gt; is correct at &lt;strong&gt;line grain&lt;/strong&gt;. If &lt;strong&gt;&lt;code&gt;products&lt;/code&gt;&lt;/strong&gt; accidentally had &lt;strong&gt;duplicate&lt;/strong&gt; rows per &lt;strong&gt;&lt;code&gt;sku&lt;/code&gt;&lt;/strong&gt;, one join could &lt;strong&gt;inflate&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;SUM(list_price)&lt;/code&gt;&lt;/strong&gt;—fix the dimension to &lt;strong&gt;one row per &lt;code&gt;sku&lt;/code&gt;&lt;/strong&gt; before summing price.&lt;/p&gt;
&lt;h4&gt;
  
  
  Inner join (&lt;code&gt;JOIN&lt;/code&gt; / &lt;code&gt;INNER JOIN&lt;/code&gt;)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; Only rows where the &lt;strong&gt;&lt;code&gt;ON&lt;/code&gt;&lt;/strong&gt; condition succeeds on &lt;strong&gt;both&lt;/strong&gt; sides.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use when:&lt;/strong&gt; You only care about “matched” pairs—e.g. clicks that have a known ad row.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt; keys:&lt;/strong&gt; In most join semantics, &lt;strong&gt;&lt;code&gt;NULL = NULL&lt;/code&gt;&lt;/strong&gt; is not true, so inner joins &lt;strong&gt;drop&lt;/strong&gt; rows with a null join key unless you &lt;strong&gt;&lt;code&gt;COALESCE&lt;/code&gt;&lt;/strong&gt; or filter explicitly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;clicks&lt;/code&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;click_id&lt;/th&gt;
&lt;th&gt;user_id&lt;/th&gt;
&lt;th&gt;ad_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;c1&lt;/td&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;c2&lt;/td&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;td&gt;99&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;ads&lt;/code&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ad_id&lt;/th&gt;
&lt;th&gt;brand_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;FROM clicks c JOIN ads a ON c.ad_id = a.ad_id&lt;/code&gt; returns &lt;strong&gt;one&lt;/strong&gt; row: &lt;strong&gt;&lt;code&gt;(c1, u1, 10, 7)&lt;/code&gt;&lt;/strong&gt;. Click &lt;strong&gt;c2&lt;/strong&gt; has &lt;strong&gt;&lt;code&gt;ad_id = 99&lt;/code&gt;&lt;/strong&gt; with no ad row, so it &lt;strong&gt;disappears&lt;/strong&gt; from an inner join.&lt;/p&gt;
&lt;h4&gt;
  
  
  Left outer join (&lt;code&gt;LEFT JOIN&lt;/code&gt;)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; &lt;strong&gt;Every&lt;/strong&gt; row from the &lt;strong&gt;left&lt;/strong&gt; table appears once per matching right row; if there is &lt;strong&gt;no&lt;/strong&gt; match, right-side columns are &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use when:&lt;/strong&gt; You must keep &lt;strong&gt;all&lt;/strong&gt; left entities even if they have zero events—e.g. all users including those with no orders.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;users&lt;/code&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Ann&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Bo&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;orders&lt;/code&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;user_id&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;o1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;FROM users u LEFT JOIN orders o ON u.id = o.user_id&lt;/code&gt; yields two rows: &lt;strong&gt;Ann + o1&lt;/strong&gt;, and &lt;strong&gt;Bo + NULL&lt;/strong&gt; (no order—right side null-padded).&lt;/p&gt;
&lt;h4&gt;
  
  
  Right outer join (&lt;code&gt;RIGHT JOIN&lt;/code&gt;)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; Mirror of &lt;strong&gt;&lt;code&gt;LEFT JOIN&lt;/code&gt;&lt;/strong&gt;: keep &lt;strong&gt;all&lt;/strong&gt; rows from the &lt;strong&gt;right&lt;/strong&gt; table; left columns &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt; when unmatched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In practice:&lt;/strong&gt; Rarely needed—&lt;strong&gt;swap tables&lt;/strong&gt; and use &lt;strong&gt;&lt;code&gt;LEFT JOIN&lt;/code&gt;&lt;/strong&gt; so “keep all rows from the table I care about” always reads the same way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; Same &lt;strong&gt;&lt;code&gt;users&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;orders&lt;/code&gt;&lt;/strong&gt; as in the left join. &lt;strong&gt;&lt;code&gt;FROM orders o RIGHT JOIN users u ON u.id = o.user_id&lt;/code&gt;&lt;/strong&gt; returns the &lt;strong&gt;same two logical rows&lt;/strong&gt; as &lt;strong&gt;&lt;code&gt;FROM users u LEFT JOIN orders o …&lt;/code&gt;&lt;/strong&gt; (&lt;strong&gt;Ann + o1&lt;/strong&gt;, &lt;strong&gt;Bo + nulls&lt;/strong&gt;)—only the &lt;strong&gt;preferred style&lt;/strong&gt; differs.&lt;/p&gt;
&lt;h4&gt;
  
  
  Full outer join (&lt;code&gt;FULL OUTER JOIN&lt;/code&gt;)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; Keeps rows that match &lt;strong&gt;plus&lt;/strong&gt; unmatched rows from &lt;strong&gt;either&lt;/strong&gt; side (missing side filled with &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use when:&lt;/strong&gt; You need a symmetric “everything from A and B” merge; less common in basic interview cards but shows up in reconciliation-style questions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; If &lt;strong&gt;&lt;code&gt;legacy&lt;/code&gt;&lt;/strong&gt; has id &lt;strong&gt;1,2&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;new&lt;/code&gt;&lt;/strong&gt; has id &lt;strong&gt;2,3&lt;/strong&gt;, a full outer join on &lt;strong&gt;&lt;code&gt;id&lt;/code&gt;&lt;/strong&gt; returns three logical keys &lt;strong&gt;1, 2, 3&lt;/strong&gt; with &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt;s where one side is missing.&lt;/p&gt;
&lt;h4&gt;
  
  
  Cross join (&lt;code&gt;CROSS JOIN&lt;/code&gt;)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; &lt;strong&gt;Cartesian product&lt;/strong&gt;: every row of A paired with every row of B.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use when:&lt;/strong&gt; You intentionally need all combinations (e.g. users × dates grid)—easy to &lt;strong&gt;explode&lt;/strong&gt; row count; use sparingly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; &lt;code&gt;stores&lt;/code&gt; has 2 rows, &lt;code&gt;quarters&lt;/code&gt; has 4 rows → &lt;strong&gt;&lt;code&gt;CROSS JOIN&lt;/code&gt;&lt;/strong&gt; produces &lt;strong&gt;8&lt;/strong&gt; rows (every store × every quarter).&lt;/p&gt;
&lt;h4&gt;
  
  
  Semi-join vs anti-join (names, not syntax)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Semi-join:&lt;/strong&gt; “Keep rows from &lt;strong&gt;A&lt;/strong&gt; where &lt;strong&gt;some&lt;/strong&gt; match exists in &lt;strong&gt;B&lt;/strong&gt;”—typically &lt;strong&gt;&lt;code&gt;WHERE EXISTS (SELECT 1 FROM B WHERE …)&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;WHERE key IN (SELECT key FROM B)&lt;/code&gt;&lt;/strong&gt;. You &lt;strong&gt;do not&lt;/strong&gt; add &lt;strong&gt;&lt;code&gt;B&lt;/code&gt;’s columns&lt;/strong&gt; to the result; you only &lt;strong&gt;filter&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;A&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anti-join:&lt;/strong&gt; “Keep rows from &lt;strong&gt;A&lt;/strong&gt; with &lt;strong&gt;no&lt;/strong&gt; match in &lt;strong&gt;B&lt;/strong&gt;”—&lt;strong&gt;&lt;code&gt;LEFT JOIN B … WHERE B.key IS NULL&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;WHERE NOT EXISTS (SELECT 1 FROM B WHERE …)&lt;/code&gt;&lt;/strong&gt;. Same idea: filter &lt;strong&gt;&lt;code&gt;A&lt;/code&gt;&lt;/strong&gt;, not a full merge for reporting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;customers&lt;/code&gt; &lt;strong&gt;&lt;code&gt;id&lt;/code&gt;:&lt;/strong&gt; &lt;strong&gt;1, 2, 3&lt;/strong&gt; — table &lt;strong&gt;&lt;code&gt;vip&lt;/code&gt;&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;customer_id&lt;/code&gt;:&lt;/strong&gt; &lt;strong&gt;2&lt;/strong&gt; only.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Semi-join:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;SELECT id FROM customers c WHERE EXISTS (SELECT 1 FROM vip v WHERE v.customer_id = c.id)&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;2&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anti-join:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;SELECT c.id FROM customers c LEFT JOIN vip v ON v.customer_id = c.id WHERE v.customer_id IS NULL&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;1&lt;/strong&gt; and &lt;strong&gt;3&lt;/strong&gt; (never appeared in &lt;strong&gt;&lt;code&gt;vip&lt;/code&gt;&lt;/strong&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Fan-out (row multiplication)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;If the join key is &lt;strong&gt;not unique&lt;/strong&gt; on one side (one user, many orders), the “one” side’s columns &lt;strong&gt;repeat&lt;/strong&gt; on every matching row. That is correct for &lt;strong&gt;order-grain&lt;/strong&gt; analysis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bug pattern:&lt;/strong&gt; Summing a &lt;strong&gt;user-level&lt;/strong&gt; numeric column after the join &lt;strong&gt;counts that user’s balance multiple times&lt;/strong&gt;. Fixes: &lt;strong&gt;aggregate first&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;SUM(DISTINCT …)&lt;/code&gt;&lt;/strong&gt; rarely fixes business logic—prefer &lt;strong&gt;&lt;code&gt;COUNT(DISTINCT user_id)&lt;/code&gt;&lt;/strong&gt; or a subquery that makes the dimension &lt;strong&gt;one row per key&lt;/strong&gt; before joining.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;users&lt;/code&gt;: &lt;strong&gt;&lt;code&gt;(id=1, credit_balance=500)&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;orders&lt;/code&gt;: two rows for &lt;strong&gt;&lt;code&gt;user_id=1&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;After &lt;strong&gt;&lt;code&gt;users JOIN orders&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;credit_balance&lt;/code&gt;&lt;/strong&gt; appears &lt;strong&gt;twice&lt;/strong&gt; (500, 500). &lt;strong&gt;&lt;code&gt;SUM(u.credit_balance)&lt;/code&gt;&lt;/strong&gt; would add &lt;strong&gt;1000&lt;/strong&gt;, which is wrong for “total credit in the system.” Fix: &lt;strong&gt;&lt;code&gt;SUM&lt;/code&gt;&lt;/strong&gt; order amounts, or &lt;strong&gt;&lt;code&gt;MAX(u.credit_balance)&lt;/code&gt;&lt;/strong&gt; per user if you only need the balance once.&lt;/p&gt;
&lt;h4&gt;
  
  
  Deduplication
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SELECT DISTINCT col&lt;/code&gt;&lt;/strong&gt; (or &lt;strong&gt;&lt;code&gt;DISTINCT&lt;/code&gt; on several columns&lt;/strong&gt;) removes duplicate &lt;strong&gt;rows&lt;/strong&gt;—fine when any duplicate row is interchangeable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latest row per id:&lt;/strong&gt; Prefer &lt;strong&gt;&lt;code&gt;ROW_NUMBER() OVER (PARTITION BY id ORDER BY time DESC) AS rn&lt;/code&gt;&lt;/strong&gt; in a CTE, then &lt;strong&gt;&lt;code&gt;WHERE rn = 1&lt;/code&gt;&lt;/strong&gt;, so you control &lt;strong&gt;ties&lt;/strong&gt; and ordering. &lt;strong&gt;&lt;code&gt;DISTINCT ON&lt;/code&gt;&lt;/strong&gt; (PostgreSQL) is a compact alternative when you know the dialect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example — &lt;code&gt;DISTINCT&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;visit_id&lt;/th&gt;
&lt;th&gt;user_id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;v1&lt;/td&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2&lt;/td&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v3&lt;/td&gt;
&lt;td&gt;u2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;SELECT DISTINCT user_id FROM visits&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;u1&lt;/strong&gt;, &lt;strong&gt;u2&lt;/strong&gt; (one row per user, duplicates collapsed).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worked example — latest event per user&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;user_id&lt;/th&gt;
&lt;th&gt;ts&lt;/th&gt;
&lt;th&gt;page&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;td&gt;t2&lt;/td&gt;
&lt;td&gt;/home&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;td&gt;t1&lt;/td&gt;
&lt;td&gt;/cart&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY ts DESC)&lt;/code&gt; gives &lt;strong&gt;1&lt;/strong&gt; for &lt;strong&gt;&lt;code&gt;(u1,t2,/home)&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;2&lt;/strong&gt; for &lt;strong&gt;&lt;code&gt;(u1,t1,/cart)&lt;/code&gt;&lt;/strong&gt;; filter &lt;strong&gt;&lt;code&gt;rn = 1&lt;/code&gt;&lt;/strong&gt; keeps only the latest row.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worked example — &lt;code&gt;DISTINCT ON&lt;/code&gt; (PostgreSQL only)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Same table as above. This picks &lt;strong&gt;one row per **&lt;code&gt;user_id&lt;/code&gt;&lt;/strong&gt;, the first row after sorting** (here, latest &lt;strong&gt;&lt;code&gt;ts&lt;/code&gt;&lt;/strong&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;DISTINCT ON (user_id)&lt;/code&gt;&lt;/strong&gt; requires &lt;strong&gt;&lt;code&gt;ORDER BY&lt;/code&gt;&lt;/strong&gt; to start with the same columns; the first row per &lt;strong&gt;&lt;code&gt;user_id&lt;/code&gt;&lt;/strong&gt; in that order is kept. For tie-breaking and clarity, &lt;strong&gt;&lt;code&gt;ROW_NUMBER()&lt;/code&gt;&lt;/strong&gt; in a CTE is often easier in interviews unless the prompt says PostgreSQL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common beginner mistakes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Joining on the &lt;strong&gt;wrong key&lt;/strong&gt; or ignoring &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt; keys (they do not match in an inner join).&lt;/li&gt;
&lt;li&gt;Assuming &lt;strong&gt;one row per user&lt;/strong&gt; after a join when the join &lt;strong&gt;multiplied&lt;/strong&gt; rows.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;COUNT(*)&lt;/code&gt; when the question wanted &lt;strong&gt;distinct people&lt;/strong&gt;—use &lt;strong&gt;&lt;code&gt;COUNT(DISTINCT user_id)&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Joins-Based Interview Question on Distinct Users and Brands
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;clicks(user_id, ad_id)&lt;/code&gt; and &lt;code&gt;ads(ad_id, brand_id)&lt;/code&gt;. How many &lt;strong&gt;distinct users&lt;/strong&gt; clicked &lt;strong&gt;any&lt;/strong&gt; ad for &lt;strong&gt;&lt;code&gt;brand_id = 7&lt;/code&gt;&lt;/strong&gt;?&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution Using INNER JOIN and COUNT DISTINCT
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;distinct_users&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;clicks&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;ads&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ad_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ad_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brand_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this works:&lt;/strong&gt; Join finds clicks whose ad belongs to brand 7; &lt;code&gt;COUNT(DISTINCT user_id)&lt;/code&gt; counts &lt;strong&gt;people&lt;/strong&gt;, not click rows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffs4jcfu5nphuptso493l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffs4jcfu5nphuptso493l.png" alt="Diagram showing a SQL fact table joining to a dimension table with row multiplication labeled as join fan-out for Meta data engineering interview prep by PipeCode." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practice&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQL · Topic —&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/topic/joins" rel="noopener noreferrer"&gt;Joins &amp;amp; deduplication&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;COMPANY · Meta — joins&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/joins" rel="noopener noreferrer"&gt;Meta-tagged joins&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Window Functions and Ranking Methods in SQL
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Window Functions and Ranking in SQL for Data Analysis
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;GROUP BY&lt;/code&gt;&lt;/strong&gt; &lt;strong&gt;shrinks&lt;/strong&gt; the table: many rows → one row per bucket. &lt;strong&gt;Window functions&lt;/strong&gt; do the opposite idea: &lt;strong&gt;every row stays&lt;/strong&gt;, but you add a &lt;strong&gt;new column&lt;/strong&gt; computed from a &lt;strong&gt;window&lt;/strong&gt; of rows around it. Syntax: &lt;strong&gt;&lt;code&gt;some_function(...) OVER (...)&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;PARTITION BY&lt;/code&gt; (optional but common)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Meaning:&lt;/strong&gt; “Start a &lt;strong&gt;new window&lt;/strong&gt; for each distinct value of these columns”—like running a separate mini-table per &lt;strong&gt;&lt;code&gt;store_id&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;user_id&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Without it:&lt;/strong&gt; The window is usually the &lt;strong&gt;whole result set&lt;/strong&gt; (after &lt;code&gt;WHERE&lt;/code&gt;), which is rare for rankings but valid for some global running totals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example (one store, running sum over days)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;store_id&lt;/th&gt;
&lt;th&gt;day&lt;/th&gt;
&lt;th&gt;revenue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;2026-01-01&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;2026-01-02&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;SUM(revenue) OVER (PARTITION BY store_id ORDER BY day ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)&lt;/code&gt; adds &lt;strong&gt;10&lt;/strong&gt; on the first row and &lt;strong&gt;40&lt;/strong&gt; on the second—&lt;strong&gt;cumulative&lt;/strong&gt; revenue for that store.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;ORDER BY&lt;/code&gt; inside &lt;code&gt;OVER&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Meaning:&lt;/strong&gt; Defines &lt;strong&gt;which row is ‘first’&lt;/strong&gt; inside each partition—required for &lt;strong&gt;&lt;code&gt;RANK&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;ROW_NUMBER&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;LAG&lt;/code&gt;/&lt;code&gt;LEAD&lt;/code&gt;&lt;/strong&gt;, and for &lt;strong&gt;ordered frames&lt;/strong&gt; (running sums).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direction:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;DESC&lt;/code&gt;&lt;/strong&gt; vs &lt;strong&gt;&lt;code&gt;ASC&lt;/code&gt;&lt;/strong&gt; changes “who is rank 1” (e.g. highest revenue first).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;store_id&lt;/th&gt;
&lt;th&gt;day&lt;/th&gt;
&lt;th&gt;revenue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;D1&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;D2&lt;/td&gt;
&lt;td&gt;150&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;RANK() OVER (PARTITION BY store_id ORDER BY revenue DESC)&lt;/code&gt;&lt;/strong&gt; gives &lt;strong&gt;1&lt;/strong&gt; on &lt;strong&gt;D1&lt;/strong&gt; and &lt;strong&gt;2&lt;/strong&gt; on &lt;strong&gt;D2&lt;/strong&gt;—highest revenue is “first” in the window because of &lt;strong&gt;&lt;code&gt;DESC&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;ROW_NUMBER()&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Assigns &lt;strong&gt;1, 2, 3, …&lt;/strong&gt; with &lt;strong&gt;no ties&lt;/strong&gt;—the database breaks ties &lt;strong&gt;arbitrarily&lt;/strong&gt; unless you add more &lt;strong&gt;&lt;code&gt;ORDER BY&lt;/code&gt;&lt;/strong&gt; keys (e.g. &lt;strong&gt;&lt;code&gt;ORDER BY revenue DESC, day ASC&lt;/code&gt;&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use when:&lt;/strong&gt; You need &lt;strong&gt;exactly one&lt;/strong&gt; row per partition (pick-one tie-break) or a unique sequence for deduplication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example (&lt;code&gt;sales&lt;/code&gt; — one row per store per day)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;store_id&lt;/th&gt;
&lt;th&gt;day&lt;/th&gt;
&lt;th&gt;revenue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;D1&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;D2&lt;/td&gt;
&lt;td&gt;150&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;D3&lt;/td&gt;
&lt;td&gt;150&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;ROW_NUMBER() OVER (PARTITION BY store_id ORDER BY revenue DESC, day ASC)&lt;/code&gt; might assign &lt;strong&gt;1&lt;/strong&gt; to D1, &lt;strong&gt;2&lt;/strong&gt; to D2, &lt;strong&gt;3&lt;/strong&gt; to D3 (tie on revenue broken by &lt;strong&gt;earlier&lt;/strong&gt; day).&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;RANK()&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ties get the same rank&lt;/strong&gt;; the next rank &lt;strong&gt;skips&lt;/strong&gt; after a tie: &lt;strong&gt;1, 2, 2, 4&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use when:&lt;/strong&gt; “Top tier” should include &lt;strong&gt;everyone tied&lt;/strong&gt; for first place, and skipped ranks are OK.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; Scores &lt;strong&gt;100, 90, 90, 80&lt;/strong&gt; → &lt;strong&gt;&lt;code&gt;RANK()&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;1, 2, 2, 4&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;DENSE_RANK()&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ties get the same rank&lt;/strong&gt;, but &lt;strong&gt;no gaps&lt;/strong&gt;: &lt;strong&gt;1, 2, 2, 3&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use when:&lt;/strong&gt; You care about “how many distinct tiers,” not Olympic-style gaps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; Same scores → &lt;strong&gt;&lt;code&gt;DENSE_RANK()&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;1, 2, 2, 3&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;NTILE(n)&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Splits each partition’s ordered rows into &lt;strong&gt;&lt;code&gt;n&lt;/code&gt;&lt;/strong&gt; &lt;strong&gt;roughly equal-sized buckets&lt;/strong&gt; (quartiles if &lt;strong&gt;&lt;code&gt;n = 4&lt;/code&gt;&lt;/strong&gt;, deciles if &lt;strong&gt;&lt;code&gt;n = 10&lt;/code&gt;&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use when:&lt;/strong&gt; “Top 10% of users by spend” style bucketing (check whether ties straddle bucket boundaries—business rules vary).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;user_id&lt;/th&gt;
&lt;th&gt;spend&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;u2&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;u3&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;u4&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;NTILE(2) OVER (ORDER BY spend ASC)&lt;/code&gt;&lt;/strong&gt; assigns &lt;strong&gt;bucket 1&lt;/strong&gt; to &lt;strong&gt;u1&lt;/strong&gt; and &lt;strong&gt;u2&lt;/strong&gt;, &lt;strong&gt;bucket 2&lt;/strong&gt; to &lt;strong&gt;u3&lt;/strong&gt; and &lt;strong&gt;u4&lt;/strong&gt; (two rows per bucket when &lt;strong&gt;&lt;code&gt;n = 2&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;n&lt;/code&gt;&lt;/strong&gt; divides evenly).&lt;/p&gt;

&lt;h4&gt;
  
  
  Window aggregates and frames (&lt;code&gt;ROWS BETWEEN …&lt;/code&gt;)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SUM(col) OVER (PARTITION BY store ORDER BY day ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)&lt;/code&gt;&lt;/strong&gt; — &lt;strong&gt;7-day trailing sum&lt;/strong&gt; per store, &lt;strong&gt;one output row per day&lt;/strong&gt; (unlike collapsing the whole table with &lt;code&gt;GROUP BY&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AVG&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;MIN&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;MAX&lt;/code&gt;&lt;/strong&gt; work similarly over a frame.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example (trailing 2-day window, illustration)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;store_id&lt;/th&gt;
&lt;th&gt;day&lt;/th&gt;
&lt;th&gt;rev&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With &lt;strong&gt;&lt;code&gt;ROWS BETWEEN 1 PRECEDING AND CURRENT ROW&lt;/code&gt;&lt;/strong&gt;, the &lt;strong&gt;2-day&lt;/strong&gt; trailing sums on rows &lt;strong&gt;1–3&lt;/strong&gt; are &lt;strong&gt;10&lt;/strong&gt;, &lt;strong&gt;30&lt;/strong&gt;, &lt;strong&gt;25&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;LAG&lt;/code&gt; / &lt;code&gt;LEAD&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;LAG(col, 1)&lt;/code&gt;&lt;/strong&gt; = previous row’s value in partition order; &lt;strong&gt;&lt;code&gt;LEAD&lt;/code&gt;&lt;/strong&gt; = next row’s—used for &lt;strong&gt;day-over-day deltas&lt;/strong&gt;, &lt;strong&gt;session gaps&lt;/strong&gt;, and &lt;strong&gt;streak&lt;/strong&gt; logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;store_id&lt;/th&gt;
&lt;th&gt;day&lt;/th&gt;
&lt;th&gt;revenue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;130&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;LAG(revenue) OVER (PARTITION BY store_id ORDER BY day)&lt;/code&gt; on the second row is &lt;strong&gt;100&lt;/strong&gt;; &lt;strong&gt;&lt;code&gt;revenue - LAG(revenue)&lt;/code&gt;&lt;/strong&gt; gives &lt;strong&gt;+30&lt;/strong&gt; day-over-day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;LEAD&lt;/code&gt; — look ahead&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Same partition order; &lt;strong&gt;&lt;code&gt;LEAD(revenue, 1)&lt;/code&gt;&lt;/strong&gt; on the &lt;strong&gt;first&lt;/strong&gt; row is &lt;strong&gt;130&lt;/strong&gt; (next day’s revenue). Use it for “how much does tomorrow differ?” or to pair each row with the &lt;strong&gt;next&lt;/strong&gt; event time when building gaps.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;FIRST_VALUE&lt;/code&gt; / &lt;code&gt;LAST_VALUE&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;FIRST_VALUE(col)&lt;/code&gt;&lt;/strong&gt; with &lt;strong&gt;&lt;code&gt;ORDER BY&lt;/code&gt;&lt;/strong&gt; in &lt;strong&gt;&lt;code&gt;OVER&lt;/code&gt;&lt;/strong&gt; is the value in the &lt;strong&gt;first&lt;/strong&gt; row of the window (often the &lt;strong&gt;first row of the partition&lt;/strong&gt; if the frame defaults to “start of partition through current row”).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;LAST_VALUE(col)&lt;/code&gt;&lt;/strong&gt; is the value in the &lt;strong&gt;last&lt;/strong&gt; row of the &lt;strong&gt;frame&lt;/strong&gt;—with the default frame, that is often &lt;strong&gt;the current row&lt;/strong&gt;, not “last row of the whole partition.” To get “last value in partition up to here,” you may need an explicit frame such as &lt;strong&gt;&lt;code&gt;ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING&lt;/code&gt;&lt;/strong&gt; (dialect-dependent), or use &lt;strong&gt;&lt;code&gt;ORDER BY … DESC&lt;/code&gt;&lt;/strong&gt; with &lt;strong&gt;&lt;code&gt;FIRST_VALUE&lt;/code&gt;&lt;/strong&gt; instead of fighting &lt;strong&gt;&lt;code&gt;LAST_VALUE&lt;/code&gt;&lt;/strong&gt; defaults.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;emp_id&lt;/th&gt;
&lt;th&gt;dept&lt;/th&gt;
&lt;th&gt;salary&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With the usual frame “from &lt;strong&gt;start of partition through current row&lt;/strong&gt;” and &lt;strong&gt;&lt;code&gt;ORDER BY salary DESC&lt;/code&gt;&lt;/strong&gt;, the &lt;strong&gt;first&lt;/strong&gt; row in that ordering is always the &lt;strong&gt;highest&lt;/strong&gt; salary (&lt;strong&gt;90&lt;/strong&gt;), so &lt;strong&gt;&lt;code&gt;FIRST_VALUE(salary) OVER (PARTITION BY dept ORDER BY salary DESC)&lt;/code&gt;&lt;/strong&gt; repeats &lt;strong&gt;90&lt;/strong&gt; on each row in dept &lt;strong&gt;A&lt;/strong&gt;—a quick way to &lt;strong&gt;annotate every line with the dept max&lt;/strong&gt; without a grouped subquery. If you need the &lt;strong&gt;minimum&lt;/strong&gt; instead, use &lt;strong&gt;&lt;code&gt;ORDER BY salary ASC&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;FIRST_VALUE&lt;/code&gt;&lt;/strong&gt; on &lt;strong&gt;&lt;code&gt;ORDER BY salary ASC&lt;/code&gt;&lt;/strong&gt; with the same pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Filtering window results:&lt;/strong&gt; You &lt;strong&gt;cannot&lt;/strong&gt; put &lt;strong&gt;&lt;code&gt;WHERE RANK() = 1&lt;/code&gt;&lt;/strong&gt; in the same &lt;code&gt;SELECT&lt;/code&gt; that defines the rank in most databases. Compute the window in an &lt;strong&gt;inner query / CTE&lt;/strong&gt;, then &lt;strong&gt;&lt;code&gt;WHERE rnk = 1&lt;/code&gt;&lt;/strong&gt; on the outside—exactly like the sample solution below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common beginner mistakes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Omitting &lt;strong&gt;&lt;code&gt;ORDER BY&lt;/code&gt;&lt;/strong&gt; inside &lt;code&gt;OVER&lt;/code&gt; when order &lt;strong&gt;defines&lt;/strong&gt; the answer.&lt;/li&gt;
&lt;li&gt;Using &lt;strong&gt;&lt;code&gt;GROUP BY&lt;/code&gt;&lt;/strong&gt; when the question still needs &lt;strong&gt;one output row per input row&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Using &lt;strong&gt;&lt;code&gt;ROW_NUMBER()&lt;/code&gt;&lt;/strong&gt; when the problem says tied rows should &lt;strong&gt;share&lt;/strong&gt; the same rank—use &lt;strong&gt;&lt;code&gt;RANK&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;DENSE_RANK&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SQL Interview Question on Window Functions and Ranking
&lt;/h3&gt;

&lt;p&gt;Table &lt;code&gt;sales(store_id, day, revenue)&lt;/code&gt; has &lt;strong&gt;one row per store per day&lt;/strong&gt;. For each &lt;strong&gt;store&lt;/strong&gt;, find the &lt;strong&gt;single best day&lt;/strong&gt; by &lt;strong&gt;highest&lt;/strong&gt; &lt;code&gt;revenue&lt;/code&gt; (if two days tie for first, returning both is fine).&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution Using RANK and a CTE
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;ranked&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;store_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;RANK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;store_id&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;revenue&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rnk&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;store_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;revenue&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ranked&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;rnk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this works:&lt;/strong&gt; &lt;code&gt;RANK()&lt;/code&gt; orders days inside each store by revenue; &lt;code&gt;rnk = 1&lt;/code&gt; keeps &lt;strong&gt;all&lt;/strong&gt; top days, including ties.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7cdz6hpja1vxidzc1v5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7cdz6hpja1vxidzc1v5.png" alt="Comparison graphic contrasting collapsed GROUP BY aggregate results with per-row window function output for PipeCode Meta data engineering SQL interview content." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practice&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQL · Topic —&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/topic/window-functions" rel="noopener noreferrer"&gt;Window functions &amp;amp; ranking&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;COMPANY · Meta — windows&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/window-functions" rel="noopener noreferrer"&gt;Meta-tagged window functions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQL · Deep dives —&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/deep-dives" rel="noopener noreferrer"&gt;SQL deep dives library&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. Subqueries and Common Table Expressions (CTEs)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Subqueries and CTEs in SQL for Data Engineering
&lt;/h3&gt;

&lt;p&gt;Many problems are &lt;strong&gt;multi-step&lt;/strong&gt;: first compute something intermediate (averages per department, revenue per day, ranked rows), then &lt;strong&gt;join or filter&lt;/strong&gt; using that result. Two ways to package that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Subquery:&lt;/strong&gt; a &lt;code&gt;SELECT&lt;/code&gt; inside parentheses in &lt;code&gt;FROM&lt;/code&gt;, &lt;code&gt;WHERE&lt;/code&gt;, or &lt;code&gt;SELECT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CTE (&lt;code&gt;WITH&lt;/code&gt;):&lt;/strong&gt; the same logic with a &lt;strong&gt;name&lt;/strong&gt; at the top: &lt;code&gt;WITH step1 AS (...) SELECT ... FROM step1 ...&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Subquery in &lt;code&gt;FROM&lt;/code&gt; (“inline view”)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pattern:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;FROM (SELECT …) AS sub&lt;/code&gt;&lt;/strong&gt; — treat the inner query like a temporary table you join or filter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use when:&lt;/strong&gt; You need a shaped intermediate dataset (deduped ranks, daily sums) before the next step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;orders&lt;/code&gt;: &lt;strong&gt;&lt;code&gt;(user_id, amount)&lt;/code&gt;&lt;/strong&gt; → inner query &lt;strong&gt;&lt;code&gt;SELECT user_id, SUM(amount) AS s FROM orders GROUP BY user_id&lt;/code&gt;&lt;/strong&gt; yields &lt;strong&gt;&lt;code&gt;(u1, 90), (u2, 40)&lt;/code&gt;&lt;/strong&gt;. Outer query &lt;strong&gt;&lt;code&gt;FROM (…) AS t JOIN profiles p ON p.id = t.user_id&lt;/code&gt;&lt;/strong&gt; attaches profile columns to each user’s total.&lt;/p&gt;

&lt;h4&gt;
  
  
  Subquery in &lt;code&gt;WHERE&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;WHERE col IN (SELECT …)&lt;/code&gt;&lt;/strong&gt; — row kept if &lt;code&gt;col&lt;/code&gt; matches &lt;strong&gt;any&lt;/strong&gt; value from the subquery (watch &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt; in the subquery with &lt;strong&gt;&lt;code&gt;NOT IN&lt;/code&gt;&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;WHERE EXISTS (SELECT1 FROM … WHERE … correlated …)&lt;/code&gt;&lt;/strong&gt; — true if &lt;strong&gt;any&lt;/strong&gt; matching row exists; often clearer than &lt;strong&gt;&lt;code&gt;IN&lt;/code&gt;&lt;/strong&gt; for existence checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalar subquery:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;WHERE amount &amp;gt; (SELECT AVG(amount) FROM …)&lt;/code&gt;&lt;/strong&gt; — inner query must return &lt;strong&gt;at most one row and one column&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;orders&lt;/code&gt;: &lt;strong&gt;&lt;code&gt;(id, dept_id, amount)&lt;/code&gt;&lt;/strong&gt; — &lt;strong&gt;&lt;code&gt;WHERE amount &amp;gt; (SELECT AVG(amount) FROM orders o2 WHERE o2.dept_id = orders.dept_id)&lt;/code&gt;&lt;/strong&gt; keeps rows &lt;strong&gt;above that row’s department average&lt;/strong&gt; (correlated scalar subquery).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worked example — &lt;code&gt;IN&lt;/code&gt; (membership)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;products&lt;/code&gt; &lt;strong&gt;&lt;code&gt;sku&lt;/code&gt;:&lt;/strong&gt; &lt;strong&gt;A, B, C&lt;/strong&gt; — &lt;code&gt;discontinued&lt;/code&gt; &lt;strong&gt;&lt;code&gt;sku&lt;/code&gt;:&lt;/strong&gt; &lt;strong&gt;B&lt;/strong&gt;. &lt;strong&gt;&lt;code&gt;SELECT sku FROM products WHERE sku IN (SELECT sku FROM discontinued)&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;B&lt;/strong&gt; only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worked example — &lt;code&gt;EXISTS&lt;/code&gt; (existence, no need to return columns from inner query)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Same tables. &lt;strong&gt;&lt;code&gt;SELECT sku FROM products p WHERE EXISTS (SELECT 1 FROM discontinued d WHERE d.sku = p.sku)&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;B&lt;/strong&gt;—same result as &lt;strong&gt;&lt;code&gt;IN&lt;/code&gt;&lt;/strong&gt; here; &lt;strong&gt;&lt;code&gt;EXISTS&lt;/code&gt;&lt;/strong&gt; often reads better when the inner query is large or correlated.&lt;/p&gt;

&lt;h4&gt;
  
  
  Subquery in &lt;code&gt;SELECT&lt;/code&gt; list
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SELECT id, (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS order_cnt&lt;/code&gt;&lt;/strong&gt; — runs &lt;strong&gt;per outer row&lt;/strong&gt;; fine in interviews; on big data often rewritten as a join for performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;users&lt;/code&gt;: Ann (&lt;strong&gt;id=1&lt;/strong&gt;), Bo (&lt;strong&gt;id=2&lt;/strong&gt;). &lt;code&gt;orders&lt;/code&gt;: two rows for Ann, none for Bo. &lt;strong&gt;&lt;code&gt;SELECT u.id, (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id)&lt;/code&gt;&lt;/strong&gt; returns &lt;strong&gt;(1, 2)&lt;/strong&gt; and &lt;strong&gt;(2, 0)&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Correlated subqueries
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;The inner query &lt;strong&gt;references&lt;/strong&gt; the outer row (e.g. &lt;strong&gt;&lt;code&gt;WHERE o.dept_id = e.dept_id&lt;/code&gt;&lt;/strong&gt; with &lt;code&gt;e&lt;/code&gt; from outside). Think: “for &lt;strong&gt;each&lt;/strong&gt; outer row, evaluate this.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;employees (id, dept_id, salary)&lt;/code&gt;: &lt;strong&gt;&lt;code&gt;(1, A, 80k), (2, A, 60k), (3, B, 90k)&lt;/code&gt;&lt;/strong&gt;. For row &lt;strong&gt;1&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;AVG(salary) WHERE dept_id = 'A'&lt;/code&gt;&lt;/strong&gt; is &lt;strong&gt;70k&lt;/strong&gt;; &lt;strong&gt;&lt;code&gt;WHERE salary &amp;gt; that avg&lt;/code&gt;&lt;/strong&gt; keeps &lt;strong&gt;row 1&lt;/strong&gt; only among dept A.&lt;/p&gt;

&lt;h4&gt;
  
  
  CTEs (&lt;code&gt;WITH&lt;/code&gt;)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Readability:&lt;/strong&gt; Steps read &lt;strong&gt;top to bottom&lt;/strong&gt; like a pipeline: clean → aggregate → join.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reuse:&lt;/strong&gt; The same CTE name can appear &lt;strong&gt;multiple times&lt;/strong&gt; in the final query; subqueries in &lt;code&gt;FROM&lt;/code&gt; must be &lt;strong&gt;duplicated&lt;/strong&gt; or wrapped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chaining:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;WITH a AS (…), b AS (SELECT … FROM a …) SELECT … FROM b&lt;/code&gt;&lt;/strong&gt; — &lt;code&gt;b&lt;/code&gt; can use &lt;code&gt;a&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recursive CTEs&lt;/strong&gt; (&lt;code&gt;WITH RECURSIVE&lt;/code&gt;): for &lt;strong&gt;trees/org charts&lt;/strong&gt;; specialty syntax—learn when you hit that problem class.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;per_day&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;revenue&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;revenue&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;per_day&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;revenue&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step &lt;strong&gt;1&lt;/strong&gt; builds a &lt;strong&gt;daily revenue&lt;/strong&gt; table; step &lt;strong&gt;2&lt;/strong&gt; filters it—same logic as an inline subquery, but easier to read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Subquery vs CTE (quick compare):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Subquery&lt;/th&gt;
&lt;th&gt;CTE (&lt;code&gt;WITH&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Readability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fine for one small nest; deep nesting gets hard to read&lt;/td&gt;
&lt;td&gt;Often &lt;strong&gt;easier to read&lt;/strong&gt;—steps read top to bottom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reuse&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Repeat the whole nest if you need it twice&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Same CTE name&lt;/strong&gt; can be referenced multiple times in one query&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Style&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;“Inline”&lt;/td&gt;
&lt;td&gt;“Named pipeline”&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Common beginner mistakes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expecting a subquery to return &lt;strong&gt;one value&lt;/strong&gt; when it returns &lt;strong&gt;many rows&lt;/strong&gt;—use &lt;strong&gt;&lt;code&gt;IN&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;EXISTS&lt;/code&gt;&lt;/strong&gt;, or a join.&lt;/li&gt;
&lt;li&gt;Nesting &lt;strong&gt;many&lt;/strong&gt; subqueries when &lt;strong&gt;&lt;code&gt;WITH&lt;/code&gt;&lt;/strong&gt; would make the steps obvious.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SQL Interview Question on Subqueries and Department Averages
&lt;/h3&gt;

&lt;p&gt;From &lt;code&gt;employees(id, dept_id, salary)&lt;/code&gt;, list employees who earn &lt;strong&gt;more than their department’s average salary&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution Using a CTE and JOIN
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;dept_avg&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;dept_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;salary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_sal&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;dept_id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dept_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;salary&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;dept_avg&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dept_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dept_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;salary&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avg_sal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this works:&lt;/strong&gt; &lt;code&gt;dept_avg&lt;/code&gt; is a &lt;strong&gt;small table&lt;/strong&gt; of one row per department; we join each employee to their department’s average and &lt;strong&gt;filter&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practice&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQL · Topic —&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/topic/ctes" rel="noopener noreferrer"&gt;Subqueries &amp;amp; CTEs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;COMPANY · Meta — CTE&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/cte" rel="noopener noreferrer"&gt;Meta-tagged CTEs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. Date Handling and Time-Series Data Concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Date and Time-Series Handling in SQL
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Time-series questions&lt;/strong&gt; almost always mean: “Put each event in a &lt;strong&gt;time bucket&lt;/strong&gt; (day, hour, week), then aggregate.”&lt;/p&gt;

&lt;h4&gt;
  
  
  Bucketing with &lt;code&gt;date_trunc&lt;/code&gt; (PostgreSQL-style)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;date_trunc('day', ts)&lt;/code&gt;&lt;/strong&gt; snaps every timestamp in that calendar day to the &lt;strong&gt;same instant&lt;/strong&gt; (midnight at the start of the day). Common grains: &lt;strong&gt;&lt;code&gt;hour&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;week&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;month&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;After truncating, &lt;strong&gt;&lt;code&gt;GROUP BY&lt;/code&gt;&lt;/strong&gt; the truncated value (or cast to &lt;strong&gt;&lt;code&gt;date&lt;/code&gt;&lt;/strong&gt;) and apply &lt;strong&gt;&lt;code&gt;SUM&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;COUNT&lt;/code&gt;&lt;/strong&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;created_at (UTC)&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2026-01-01 22:00&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-01-02 03:00&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both fall on different calendar &lt;strong&gt;days&lt;/strong&gt; in UTC, so &lt;strong&gt;&lt;code&gt;GROUP BY date_trunc('day', created_at)::date&lt;/code&gt;&lt;/strong&gt; gives &lt;strong&gt;two&lt;/strong&gt; buckets: &lt;strong&gt;2026-01-01 → 10&lt;/strong&gt;, &lt;strong&gt;2026-01-02 → 20&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;INTERVAL&lt;/code&gt; and relative windows
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;CURRENT_DATE - INTERVAL '7 days'&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;NOW() - INTERVAL '1 hour'&lt;/code&gt;&lt;/strong&gt; expresses sliding windows without hard-coding calendar dates.&lt;/li&gt;
&lt;li&gt;Pair with &lt;strong&gt;&lt;code&gt;WHERE ts &amp;gt;= …&lt;/code&gt;&lt;/strong&gt; (and usually &lt;strong&gt;&lt;code&gt;&amp;lt;&lt;/code&gt;&lt;/strong&gt; end for half-open ranges).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; “Last 24 hours” from &lt;strong&gt;&lt;code&gt;NOW()&lt;/code&gt;&lt;/strong&gt;: &lt;strong&gt;&lt;code&gt;WHERE event_ts &amp;gt;= NOW() - INTERVAL '24 hours'&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;EXTRACT&lt;/code&gt; / &lt;code&gt;date_part&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Pull &lt;strong&gt;hour of day&lt;/strong&gt;, &lt;strong&gt;dow&lt;/strong&gt; (day of week), &lt;strong&gt;month&lt;/strong&gt;, etc. for “volume by hour” or seasonality slices: &lt;strong&gt;&lt;code&gt;EXTRACT(HOUR FROM ts)&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2026-01-01 08:30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026-01-01 09:15&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;GROUP BY EXTRACT(HOUR FROM ts)&lt;/code&gt;&lt;/strong&gt; groups both into hours &lt;strong&gt;8&lt;/strong&gt; and &lt;strong&gt;9&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Difference between timestamps
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;end_ts - start_ts&lt;/code&gt;&lt;/strong&gt; (PostgreSQL &lt;strong&gt;&lt;code&gt;interval&lt;/code&gt;&lt;/strong&gt;) or &lt;strong&gt;&lt;code&gt;DATEDIFF&lt;/code&gt;&lt;/strong&gt;-style functions in other engines—useful for &lt;strong&gt;durations&lt;/strong&gt; and “time between events.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ordered_at&lt;/th&gt;
&lt;th&gt;shipped_at&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2026-01-01 10:00&lt;/td&gt;
&lt;td&gt;2026-01-01 16:00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;shipped_at - ordered_at&lt;/code&gt;&lt;/strong&gt; is a &lt;strong&gt;6-hour&lt;/strong&gt; interval (cast to minutes/seconds if the question asks for a number).&lt;/p&gt;

&lt;h4&gt;
  
  
  Rolling metrics (window frames)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SUM(amount) OVER (PARTITION BY store_id ORDER BY day ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)&lt;/code&gt;&lt;/strong&gt; = &lt;strong&gt;7-day trailing sum&lt;/strong&gt; per store, &lt;strong&gt;one row per day&lt;/strong&gt; still present.&lt;/li&gt;
&lt;li&gt;Change &lt;strong&gt;&lt;code&gt;ROWS&lt;/code&gt;&lt;/strong&gt; to &lt;strong&gt;&lt;code&gt;RANGE&lt;/code&gt;&lt;/strong&gt; only when you understand your SQL dialect’s semantics for gaps in dates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example (7-day trailing sum, one row per day stays)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;store_id&lt;/th&gt;
&lt;th&gt;day&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;2026-01-01&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;2026-01-02&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;2026-01-03&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;2026-01-04&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;2026-01-05&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;2026-01-06&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;2026-01-07&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;SUM(amount) OVER (PARTITION BY store_id ORDER BY day ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)&lt;/code&gt;&lt;/strong&gt; on &lt;strong&gt;2026-01-07&lt;/strong&gt; is &lt;strong&gt;65&lt;/strong&gt; (sum of all &lt;strong&gt;seven&lt;/strong&gt; days)—same pattern as section 4’s shorter &lt;strong&gt;2-day&lt;/strong&gt; illustration, stretched to a &lt;strong&gt;week&lt;/strong&gt; of daily facts.&lt;/p&gt;

&lt;h4&gt;
  
  
  Boundaries: half-open vs &lt;code&gt;BETWEEN&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Prefer &lt;strong&gt;&lt;code&gt;ts &amp;gt;= start_ts AND ts &amp;lt; end_ts&lt;/code&gt;&lt;/strong&gt; so an event &lt;strong&gt;exactly&lt;/strong&gt; on &lt;code&gt;end_ts&lt;/code&gt; is not counted twice across adjacent windows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;BETWEEN a AND b&lt;/code&gt;&lt;/strong&gt; includes &lt;strong&gt;both&lt;/strong&gt; endpoints—fine for inclusive business dates; easy to off-by-one with timestamps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; “All of January 2026” → &lt;strong&gt;&lt;code&gt;WHERE ts &amp;gt;= TIMESTAMP '2026-01-01' AND ts &amp;lt; TIMESTAMP '2026-02-01'&lt;/code&gt;&lt;/strong&gt; (midnight &lt;strong&gt;Feb1&lt;/strong&gt; is excluded).&lt;/p&gt;

&lt;h4&gt;
  
  
  Time zones
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Store and compare in &lt;strong&gt;UTC&lt;/strong&gt; internally when possible; &lt;strong&gt;convert to local&lt;/strong&gt; before &lt;strong&gt;&lt;code&gt;date_trunc('day', …)&lt;/code&gt;&lt;/strong&gt; if “calendar day” means a specific region.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; Event at &lt;strong&gt;&lt;code&gt;2026-01-02 02:00 UTC&lt;/code&gt;&lt;/strong&gt; might be &lt;strong&gt;Jan 1 evening&lt;/strong&gt; in New York—bucket by &lt;strong&gt;local&lt;/strong&gt; day if the metric is “sales per US calendar day.”&lt;/p&gt;

&lt;h4&gt;
  
  
  Gaps, sessions, and neighbors
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Sort by time, then &lt;strong&gt;&lt;code&gt;LAG(ts)&lt;/code&gt;&lt;/strong&gt; / &lt;strong&gt;&lt;code&gt;LEAD(ts)&lt;/code&gt;&lt;/strong&gt; to compute gaps between consecutive events—foundation for &lt;strong&gt;sessionization&lt;/strong&gt; and &lt;strong&gt;streak&lt;/strong&gt; problems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;user_id&lt;/th&gt;
&lt;th&gt;ts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;td&gt;t0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;td&gt;t0 + 5 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;td&gt;t0 + 2 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Gaps are &lt;strong&gt;5 minutes&lt;/strong&gt; then &lt;strong&gt;115 minutes&lt;/strong&gt;; a &lt;strong&gt;30-minute&lt;/strong&gt; session timeout would &lt;strong&gt;break&lt;/strong&gt; a new session after the second event.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common beginner mistakes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mixing &lt;strong&gt;&lt;code&gt;DATE&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;TIMESTAMP&lt;/code&gt;&lt;/strong&gt; without &lt;strong&gt;&lt;code&gt;::date&lt;/code&gt;&lt;/strong&gt; or casting.&lt;/li&gt;
&lt;li&gt;Using &lt;strong&gt;&lt;code&gt;BETWEEN&lt;/code&gt;&lt;/strong&gt; on timestamps when a half-open range is safer.&lt;/li&gt;
&lt;li&gt;Forgetting &lt;strong&gt;UTC vs local&lt;/strong&gt; when filtering “today.”&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SQL Interview Question on Dates and Daily Totals
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;orders(order_id, created_at, amount)&lt;/code&gt; in UTC. Return &lt;strong&gt;total &lt;code&gt;amount&lt;/code&gt; per calendar day&lt;/strong&gt; for the &lt;strong&gt;last 7 days&lt;/strong&gt; (relative to &lt;code&gt;CURRENT_DATE&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution Using date_trunc and GROUP BY
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;date_trunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'day'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;order_day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;daily_total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'7 days'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this works:&lt;/strong&gt; We filter to the rolling window, &lt;strong&gt;truncate&lt;/strong&gt; each &lt;code&gt;created_at&lt;/code&gt; to midnight, and &lt;strong&gt;sum&lt;/strong&gt; per day.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiidk465s5k4aur25dghb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiidk465s5k4aur25dghb.png" alt="Timeline diagram of hourly buckets with error events and gap arrows illustrating time-series SQL interview concepts for PipeCode Meta DE prep." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practice&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQL · Topic —&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/topic/date-functions" rel="noopener noreferrer"&gt;Date functions &amp;amp; time buckets&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;COMPANY · Meta — dates&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/date-functions" rel="noopener noreferrer"&gt;Meta-tagged date functions&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. Handling NULL Values and Safe Calculations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  NULL Handling and Safe Calculations in SQL
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt; means &lt;strong&gt;“we don’t know the value”&lt;/strong&gt;—not zero, not “false,” not an empty string.&lt;/p&gt;

&lt;h4&gt;
  
  
  In &lt;code&gt;WHERE&lt;/code&gt; and expressions
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;WHERE col = NULL&lt;/code&gt;&lt;/strong&gt; is invalid / wrong; use &lt;strong&gt;&lt;code&gt;IS NULL&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;IS NOT NULL&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;COALESCE(a, b)&lt;/code&gt;&lt;/strong&gt; returns the first &lt;strong&gt;non-null&lt;/strong&gt; argument—use when the business says “treat unknown as default X.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;discount_pct&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;SELECT COALESCE(discount_pct, 0)&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;0&lt;/strong&gt; and &lt;strong&gt;10&lt;/strong&gt;. &lt;strong&gt;&lt;code&gt;WHERE discount_pct IS NULL&lt;/code&gt;&lt;/strong&gt; returns row &lt;strong&gt;1&lt;/strong&gt; only.&lt;/p&gt;

&lt;h4&gt;
  
  
  How aggregates treat &lt;code&gt;NULL&lt;/code&gt; (per bucket)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SUM&lt;/code&gt; / &lt;code&gt;AVG&lt;/code&gt; / &lt;code&gt;MIN&lt;/code&gt; / &lt;code&gt;MAX&lt;/code&gt;:&lt;/strong&gt; &lt;code&gt;NULL&lt;/code&gt; inputs are &lt;strong&gt;skipped&lt;/strong&gt;; if there is nothing left to aggregate, the result is often &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;COUNT(*)&lt;/code&gt;:&lt;/strong&gt; Counts &lt;strong&gt;rows&lt;/strong&gt;, regardless of nulls in individual columns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;COUNT(col)&lt;/code&gt;:&lt;/strong&gt; Counts rows where &lt;strong&gt;&lt;code&gt;col&lt;/code&gt; is not &lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;COUNT(DISTINCT col)&lt;/code&gt;:&lt;/strong&gt; Counts &lt;strong&gt;distinct non-null&lt;/strong&gt; values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example (one group, three rows)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;SUM&lt;/code&gt;&lt;/strong&gt; = &lt;strong&gt;40&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;AVG&lt;/code&gt;&lt;/strong&gt; = &lt;strong&gt;20&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;COUNT(*)&lt;/code&gt;&lt;/strong&gt; = &lt;strong&gt;3&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;COUNT(amount)&lt;/code&gt;&lt;/strong&gt; = &lt;strong&gt;2&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Percentages and rates
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Write the definition explicitly: &lt;strong&gt;numerator&lt;/strong&gt; = rows (or sum) matching success; &lt;strong&gt;denominator&lt;/strong&gt; = &lt;strong&gt;all rows in scope&lt;/strong&gt; (or a filtered population). “Completion rate” changes if the denominator is “all tasks” vs “tasks that started.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;COUNT(*) FILTER (WHERE condition)&lt;/code&gt;&lt;/strong&gt; (PostgreSQL) builds numerators/denominators in one grouped query without subqueries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SUM(CASE WHEN condition THEN 1 ELSE 0 END) * 1.0 / COUNT(*)&lt;/code&gt;&lt;/strong&gt; is the portable analog.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;task_id&lt;/th&gt;
&lt;th&gt;status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;done&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Done fraction&lt;/strong&gt; = &lt;strong&gt;2 / 3&lt;/strong&gt;, i.e. about &lt;strong&gt;0.667&lt;/strong&gt;. Query: &lt;strong&gt;&lt;code&gt;COUNT(*) FILTER (WHERE status = 'done')::numeric / NULLIF(COUNT(*), 0)&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;0.666…&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Divide-by-zero
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;If the denominator can be &lt;strong&gt;0&lt;/strong&gt;, use &lt;strong&gt;&lt;code&gt;NULLIF(denominator, 0)&lt;/code&gt;&lt;/strong&gt; so the division yields &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt; instead of an error—then handle in the app or outer query.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;player_id&lt;/th&gt;
&lt;th&gt;hits&lt;/th&gt;
&lt;th&gt;at_bats&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;hits::numeric / NULLIF(at_bats, 0)&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt;, not a runtime error.&lt;/p&gt;

&lt;h4&gt;
  
  
  Empty input
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No rows&lt;/strong&gt; after &lt;code&gt;WHERE&lt;/code&gt;: aggregates like &lt;strong&gt;&lt;code&gt;COUNT(*)&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;0&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;SUM&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt; (typical)—state assumptions out loud in an interview.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;SELECT SUM(amount) FROM orders WHERE FALSE&lt;/code&gt;&lt;/strong&gt; — no rows match → &lt;strong&gt;&lt;code&gt;SUM&lt;/code&gt;&lt;/strong&gt; is &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt; in PostgreSQL; &lt;strong&gt;&lt;code&gt;COUNT(*)&lt;/code&gt;&lt;/strong&gt; would be &lt;strong&gt;0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common beginner mistakes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Treating &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt; like &lt;strong&gt;0&lt;/strong&gt; in business logic.&lt;/li&gt;
&lt;li&gt;Using the wrong &lt;strong&gt;denominator&lt;/strong&gt; for a rate.&lt;/li&gt;
&lt;li&gt;Dividing without guarding &lt;strong&gt;zero&lt;/strong&gt; with &lt;strong&gt;&lt;code&gt;NULLIF&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SQL Interview Question on NULL-Safe Rates and Fractions
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;tasks(task_id, status)&lt;/code&gt; where &lt;code&gt;status&lt;/code&gt; is &lt;code&gt;'done'&lt;/code&gt; or &lt;code&gt;'open'&lt;/code&gt;. What &lt;strong&gt;fraction&lt;/strong&gt; of tasks are &lt;strong&gt;&lt;code&gt;done&lt;/code&gt;&lt;/strong&gt;?&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution Using FILTER and NULLIF
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'done'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;numeric&lt;/span&gt;
       &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;NULLIF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;done_fraction&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this works:&lt;/strong&gt; Numerator = done rows; denominator = &lt;strong&gt;all&lt;/strong&gt; tasks; &lt;code&gt;NULLIF&lt;/code&gt; prevents divide-by-zero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practice&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQL · Topic —&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/topic/null-handling" rel="noopener noreferrer"&gt;NULL handling &amp;amp; safe rates&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;COMPANY · Meta — hub&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/company/meta" rel="noopener noreferrer"&gt;Meta SQL &amp;amp; Python (includes null-edge drills)&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  8. Set Operations and Data Comparison Techniques
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Set Operations in SQL (UNION, INTERSECT, EXCEPT)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Set problems&lt;/strong&gt; sound like: “users in &lt;strong&gt;both&lt;/strong&gt; A and B,” “customers who bought &lt;strong&gt;X but not&lt;/strong&gt; Y,” “combine two lists of ids.” You are doing &lt;strong&gt;intersection&lt;/strong&gt;, &lt;strong&gt;difference&lt;/strong&gt;, or &lt;strong&gt;union&lt;/strong&gt; on &lt;strong&gt;keys&lt;/strong&gt; (usually &lt;code&gt;user_id&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Set operators require both branches to return the &lt;strong&gt;same number of columns&lt;/strong&gt; with &lt;strong&gt;compatible types&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;INTERSECT&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; Only rows that appear in &lt;strong&gt;both&lt;/strong&gt; &lt;code&gt;SELECT&lt;/code&gt;s (duplicate handling depends on dialect; often &lt;strong&gt;distinct&lt;/strong&gt; rows).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interview mapping:&lt;/strong&gt; “Users who did &lt;strong&gt;both&lt;/strong&gt; action A and action B” when each branch returns the same key column.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mobile&lt;/code&gt;: user_ids &lt;strong&gt;u1, u2&lt;/strong&gt; — &lt;code&gt;web&lt;/code&gt;: &lt;strong&gt;u2, u3&lt;/strong&gt; — &lt;strong&gt;&lt;code&gt;INTERSECT&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;u2&lt;/strong&gt; only.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;EXCEPT&lt;/code&gt; (some engines: &lt;code&gt;MINUS&lt;/code&gt;)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; Rows in the &lt;strong&gt;first&lt;/strong&gt; query &lt;strong&gt;not&lt;/strong&gt; present in the second.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interview mapping:&lt;/strong&gt; “Signed up but never purchased,” “In feed A but not in feed B.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;signups&lt;/code&gt;: &lt;strong&gt;u1, u2&lt;/strong&gt; — &lt;code&gt;buyers&lt;/code&gt;: &lt;strong&gt;u2&lt;/strong&gt; — &lt;strong&gt;&lt;code&gt;EXCEPT&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;u1&lt;/strong&gt; (“signed up, never bought” if &lt;code&gt;buyers&lt;/code&gt; is “ever purchased”).&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;UNION&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; Stack the two result sets and &lt;strong&gt;deduplicate&lt;/strong&gt; rows (can sort + dedupe—often more expensive than &lt;strong&gt;&lt;code&gt;UNION ALL&lt;/code&gt;&lt;/strong&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;SELECT id FROM a&lt;/code&gt;&lt;/strong&gt; yields &lt;strong&gt;1,1,2&lt;/strong&gt;; &lt;strong&gt;&lt;code&gt;SELECT id FROM b&lt;/code&gt;&lt;/strong&gt; yields &lt;strong&gt;2,3&lt;/strong&gt; — &lt;strong&gt;&lt;code&gt;UNION&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;1, 2, 3&lt;/strong&gt; (unique).&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;UNION ALL&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; Stack results &lt;strong&gt;keeping all duplicates&lt;/strong&gt;—preferred when you know duplicates are impossible or when you &lt;strong&gt;want&lt;/strong&gt; repeated rows (e.g. concatenating event streams).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; Same as above → &lt;strong&gt;&lt;code&gt;UNION ALL&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;1,1,2,2,3&lt;/strong&gt; (five rows).&lt;/p&gt;

&lt;h4&gt;
  
  
  Join and &lt;code&gt;EXISTS&lt;/code&gt; equivalents
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Intersection on keys:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;INNER JOIN&lt;/code&gt;&lt;/strong&gt; on &lt;code&gt;user_id&lt;/code&gt; from two deduped subqueries—or &lt;strong&gt;&lt;code&gt;WHERE EXISTS&lt;/code&gt;&lt;/strong&gt; for semi-join style.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Difference (A not B):&lt;/strong&gt; &lt;strong&gt;Anti-join:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;LEFT JOIN B ON … WHERE B.key IS NULL&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;NOT EXISTS (SELECT 1 FROM B WHERE …)&lt;/code&gt;&lt;/strong&gt; is often &lt;strong&gt;safer than &lt;code&gt;NOT IN&lt;/code&gt;&lt;/strong&gt; when &lt;code&gt;B&lt;/code&gt; can produce &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt; keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example (semi-join with &lt;code&gt;IN&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;customers&lt;/code&gt; &lt;strong&gt;1, 2, 3&lt;/strong&gt; — &lt;strong&gt;&lt;code&gt;vip&lt;/code&gt;&lt;/strong&gt; has &lt;strong&gt;&lt;code&gt;customer_id&lt;/code&gt;&lt;/strong&gt; &lt;strong&gt;2&lt;/strong&gt; only. &lt;strong&gt;&lt;code&gt;SELECT id FROM customers WHERE id IN (SELECT customer_id FROM vip)&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;2&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worked example (anti-join)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;users&lt;/code&gt; &lt;strong&gt;1,2&lt;/strong&gt; — &lt;code&gt;buyers&lt;/code&gt; only &lt;strong&gt;user 2&lt;/strong&gt;. &lt;strong&gt;&lt;code&gt;FROM users u LEFT JOIN buyers b ON u.id = b.user_id WHERE b.user_id IS NULL&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;user 1&lt;/strong&gt; (not in buyers).&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;GROUP BY&lt;/code&gt; + &lt;code&gt;HAVING&lt;/code&gt; for “both” conditions
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;HAVING COUNT(DISTINCT CASE WHEN … THEN tag END) = 2&lt;/code&gt;&lt;/strong&gt; (or two boolean conditions) can express “user did &lt;strong&gt;both&lt;/strong&gt; activities” in one fact table without set operators—useful when set SQL is awkward or slow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;purchases&lt;/code&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;user_id&lt;/th&gt;
&lt;th&gt;product_code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;td&gt;BOOK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;u1&lt;/td&gt;
&lt;td&gt;PEN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;u2&lt;/td&gt;
&lt;td&gt;BOOK&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;GROUP BY user_id HAVING MAX(CASE WHEN product_code = 'BOOK' THEN 1 ELSE 0 END) = 1 AND MAX(CASE WHEN product_code = 'PEN' THEN 1 ELSE 0 END) = 1&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;u1&lt;/strong&gt; only (has both).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common beginner mistakes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using &lt;strong&gt;&lt;code&gt;OR&lt;/code&gt;&lt;/strong&gt; when the problem needs &lt;strong&gt;both&lt;/strong&gt; conditions on the &lt;strong&gt;same user&lt;/strong&gt; (not “either event”).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;NOT IN (subquery)&lt;/code&gt;&lt;/strong&gt; when the subquery can return &lt;strong&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/strong&gt;—use &lt;strong&gt;&lt;code&gt;NOT EXISTS&lt;/code&gt;&lt;/strong&gt; instead.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SQL Interview Question on INTERSECT and Multiple Purchases
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;purchases(user_id, product_code)&lt;/code&gt;. Find &lt;code&gt;user_id&lt;/code&gt;s who bought &lt;strong&gt;&lt;code&gt;BOOK&lt;/code&gt;&lt;/strong&gt; &lt;strong&gt;and&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;PEN&lt;/code&gt;&lt;/strong&gt; (possibly on different rows).&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution Using INTERSECT
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;purchases&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;product_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'BOOK'&lt;/span&gt;
&lt;span class="k"&gt;INTERSECT&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;purchases&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;product_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'PEN'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this works:&lt;/strong&gt; First query = set of users with BOOK; second = set with PEN; &lt;strong&gt;intersect&lt;/strong&gt; = users in &lt;strong&gt;both&lt;/strong&gt; sets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practice&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQL · Topic —&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/topic/set-operations" rel="noopener noreferrer"&gt;Set operations (INTERSECT, EXCEPT, …)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;COMPANY · Meta — sets&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/set" rel="noopener noreferrer"&gt;Meta-tagged set logic&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  9. Hash Maps and Counting Techniques in Python
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Hash Maps and Counting in Python for Data Processing
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;&lt;code&gt;dict&lt;/code&gt;&lt;/strong&gt; (&lt;strong&gt;hash map&lt;/strong&gt;) maps &lt;strong&gt;keys&lt;/strong&gt; to &lt;strong&gt;values&lt;/strong&gt;. Average-time &lt;strong&gt;lookup, insert, and update&lt;/strong&gt; are &lt;strong&gt;O(1)&lt;/strong&gt; in the usual amortized sense—much faster than rescanning a whole list for every key.&lt;/p&gt;

&lt;h4&gt;
  
  
  Plain &lt;code&gt;dict&lt;/code&gt; and &lt;code&gt;.get&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frequency:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;counts[k] = counts.get(k, 0) + 1&lt;/code&gt;&lt;/strong&gt; avoids &lt;strong&gt;&lt;code&gt;KeyError&lt;/code&gt;&lt;/strong&gt; on first sighting of &lt;code&gt;k&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grouping into lists:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;d.setdefault(k, []).append(item)&lt;/code&gt;&lt;/strong&gt; or use &lt;strong&gt;&lt;code&gt;defaultdict&lt;/code&gt;&lt;/strong&gt; below.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;words = ["cat", "dog", "cat"]&lt;/code&gt; — after the loop, &lt;strong&gt;&lt;code&gt;freq&lt;/code&gt;&lt;/strong&gt; is &lt;strong&gt;&lt;code&gt;{"cat": 2, "dog": 1}&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;collections.Counter&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Built for &lt;strong&gt;frequency counts&lt;/strong&gt;: &lt;strong&gt;&lt;code&gt;Counter(iterable)&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;.most_common(k)&lt;/code&gt;&lt;/strong&gt; for top-k.&lt;/li&gt;
&lt;li&gt;Good when the problem is “how many of each label?” with minimal code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;err&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;err&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;most_common&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# [('err', 2)]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;defaultdict&lt;/code&gt; from &lt;code&gt;collections&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;defaultdict(int)&lt;/code&gt;&lt;/strong&gt; — same ergonomics as counting with 0 default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;defaultdict(list)&lt;/code&gt;&lt;/strong&gt; — &lt;strong&gt;&lt;code&gt;d[user_id].append(event)&lt;/code&gt;&lt;/strong&gt; mirrors &lt;strong&gt;&lt;code&gt;GROUP BY user_id&lt;/code&gt;&lt;/strong&gt; “collect all rows in a bucket.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;defaultdict(set)&lt;/code&gt;&lt;/strong&gt; — handy for &lt;strong&gt;unique&lt;/strong&gt; neighbors per key.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Rows &lt;strong&gt;&lt;code&gt;("u1", "click")&lt;/code&gt;, &lt;code&gt;("u1", "view")&lt;/code&gt;&lt;/strong&gt; — &lt;strong&gt;&lt;code&gt;dd["u1"]&lt;/code&gt;&lt;/strong&gt; becomes &lt;strong&gt;&lt;code&gt;["click", "view"]&lt;/code&gt;&lt;/strong&gt; with &lt;strong&gt;&lt;code&gt;defaultdict(list)&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;defaultdict(set)&lt;/code&gt;:&lt;/strong&gt; Edges &lt;strong&gt;&lt;code&gt;("u1","u2"), ("u1","u3"), ("u1","u2")&lt;/code&gt;&lt;/strong&gt; — &lt;strong&gt;&lt;code&gt;g["u1"].add(...)&lt;/code&gt;&lt;/strong&gt; yields &lt;strong&gt;&lt;code&gt;{"u2", "u3"}&lt;/code&gt;&lt;/strong&gt; (duplicates collapsed).&lt;/p&gt;

&lt;h4&gt;
  
  
  Multi-column &lt;code&gt;GROUP BY&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Use a &lt;strong&gt;tuple key:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;key = (country, day)&lt;/code&gt;&lt;/strong&gt; as the dict key when the bucket is more than one dimension.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;totals = {}&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;totals[("US", "2026-01-01")] = totals.get(("US", "2026-01-01"), 0) + 50&lt;/code&gt;&lt;br&gt;&lt;br&gt;
→ one bucket per &lt;strong&gt;(country, day)&lt;/strong&gt; pair.&lt;/p&gt;
&lt;h4&gt;
  
  
  Simulating SQL aggregates
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SQL idea&lt;/th&gt;
&lt;th&gt;Python sketch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;COUNT(*)&lt;/code&gt; per key&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;counts[k] += 1&lt;/code&gt; or &lt;code&gt;Counter&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;SUM(amount)&lt;/code&gt; per key&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sums[k] = sums.get(k, 0) + amount&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;AVG(amount)&lt;/code&gt; per key&lt;/td&gt;
&lt;td&gt;Store &lt;strong&gt;&lt;code&gt;(sum, count)&lt;/code&gt;&lt;/strong&gt; per key, divide at the end&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;COUNT(DISTINCT x)&lt;/code&gt; per key&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;d[k].add(x)&lt;/code&gt; with a &lt;strong&gt;&lt;code&gt;set&lt;/code&gt;&lt;/strong&gt; per key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rows = [{"user": "a", "amt": 10}, {"user": "a", "amt": 20}, {"user": "b", "amt": 5}]&lt;/code&gt;&lt;br&gt;&lt;br&gt;
One pass: &lt;strong&gt;&lt;code&gt;sums["a"]=30&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;sums["b"]=5&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worked example — distinct per key&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rows = [{"user": "a", "sku": "X"}, {"user": "a", "sku": "Y"}, {"user": "a", "sku": "X"}]&lt;/code&gt; — after &lt;strong&gt;&lt;code&gt;uniq["a"].add(sku)&lt;/code&gt;&lt;/strong&gt; with &lt;strong&gt;&lt;code&gt;defaultdict(set)&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;len(uniq["a"]) == 2&lt;/code&gt;&lt;/strong&gt; (matches &lt;strong&gt;&lt;code&gt;COUNT(DISTINCT sku) GROUP BY user&lt;/code&gt;&lt;/strong&gt;).&lt;/p&gt;
&lt;h4&gt;
  
  
  Why avoid nested scans?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;“For each distinct key, loop the entire list” is &lt;strong&gt;O(n²)&lt;/strong&gt;. One pass with a dict is typically &lt;strong&gt;O(n)&lt;/strong&gt; time and &lt;strong&gt;O(distinct keys)&lt;/strong&gt; space—what interviewers expect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; With about &lt;strong&gt;10,000&lt;/strong&gt; rows and &lt;strong&gt;1,000&lt;/strong&gt; distinct keys, rescanning all rows per key is on the order of &lt;strong&gt;ten million&lt;/strong&gt; operations; one dict pass stays on the order of &lt;strong&gt;ten thousand&lt;/strong&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;heapq&lt;/code&gt; for top-k (when k is small)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;heapq.nlargest(k, iterable)&lt;/code&gt;&lt;/strong&gt; / &lt;strong&gt;&lt;code&gt;nsmallest&lt;/code&gt;&lt;/strong&gt; return the &lt;strong&gt;k&lt;/strong&gt; best items without sorting the whole list (&lt;strong&gt;O(n log k)&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;For “top k keys by count,” you can also push &lt;strong&gt;(-count, key)&lt;/strong&gt; into a &lt;strong&gt;min-heap&lt;/strong&gt; of size &lt;strong&gt;k&lt;/strong&gt; while streaming counts—useful when memory must stay &lt;strong&gt;O(k)&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;heapq&lt;/span&gt;
&lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;top2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;heapq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nlargest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# [('c', 9), ('a', 5)]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Common beginner mistakes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Re-scanning the whole list inside a loop over unique keys.&lt;/li&gt;
&lt;li&gt;Forgetting &lt;strong&gt;tie-breaking&lt;/strong&gt; (e.g. lexicographically smallest among max frequency).&lt;/li&gt;
&lt;li&gt;Not handling &lt;strong&gt;missing keys&lt;/strong&gt;—use &lt;strong&gt;&lt;code&gt;.get(key, 0)&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;Counter&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Python Interview Question on Frequency and Tie-Breaking
&lt;/h3&gt;

&lt;p&gt;Given a list of words, return the &lt;strong&gt;word with the highest count&lt;/strong&gt; (break ties by &lt;strong&gt;lexicographically smallest&lt;/strong&gt; word).&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution Using Counter and min
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;top_word&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cnt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;max_freq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cnt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;max_freq&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Example: top_word(["apple", "banana", "apple"]) -&amp;gt; "apple"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this works:&lt;/strong&gt; &lt;code&gt;Counter&lt;/code&gt; gets frequencies; we keep all words at the max count; &lt;code&gt;min&lt;/code&gt; picks the tie-breaker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practice&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PYTHON · Topic —&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/topic/hash-table" rel="noopener noreferrer"&gt;Hash tables &amp;amp; counting&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;COMPANY · Meta — hash table&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/hash-table" rel="noopener noreferrer"&gt;Meta-tagged hash tables&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  10. Streaming Data and Interval Processing in Python
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Streaming Data Processing in Python
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Streaming&lt;/strong&gt; means: events arrive &lt;strong&gt;one after another&lt;/strong&gt; (or in time order). You keep a &lt;strong&gt;small&lt;/strong&gt; piece of state—last timestamp seen, counts in the current window, “open” orders in a dict—and &lt;strong&gt;update&lt;/strong&gt; it when the next event arrives. You should &lt;strong&gt;not&lt;/strong&gt; rescan the entire history for each new line if you want an efficient solution.&lt;/p&gt;

&lt;h4&gt;
  
  
  Sorting event streams
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Most simulations assume &lt;strong&gt;&lt;code&gt;events.sort()&lt;/code&gt;&lt;/strong&gt; by timestamp (and &lt;strong&gt;stable tie-breaking&lt;/strong&gt;: e.g. process &lt;strong&gt;end&lt;/strong&gt; before &lt;strong&gt;start&lt;/strong&gt; at the same time if “touching” is not overlap—depends on problem statement).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;events = [(10, "start"), (5, "end"), (7, "start")]&lt;/code&gt; — sort by time → &lt;strong&gt;&lt;code&gt;(5,end), (7,start), (10,start)&lt;/code&gt;&lt;/strong&gt; so processing order matches the real timeline.&lt;/p&gt;

&lt;h4&gt;
  
  
  Half-open intervals &lt;code&gt;[start, end)&lt;/code&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start included, end excluded&lt;/strong&gt;—standard for “busy from second &lt;code&gt;start&lt;/code&gt; up to but not including &lt;code&gt;end&lt;/code&gt;.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overlap test&lt;/strong&gt; for &lt;strong&gt;&lt;code&gt;[a1, a2)&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;[b1, b2)&lt;/code&gt;:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;a1 &amp;lt; b2&lt;/code&gt; and &lt;code&gt;a2 &amp;gt; b1&lt;/code&gt;&lt;/strong&gt;. (Closed intervals use different inequalities—match the prompt.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;[1, 5)&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;[5, 8)&lt;/code&gt;&lt;/strong&gt; — touch at &lt;strong&gt;5&lt;/strong&gt; but &lt;strong&gt;no overlap&lt;/strong&gt; (half-open).&lt;/p&gt;

&lt;h4&gt;
  
  
  Merge overlapping intervals
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Sort by start, then sweep: if the next interval &lt;strong&gt;overlaps&lt;/strong&gt; the current merged block, extend the block’s end with &lt;strong&gt;&lt;code&gt;max(end1, end2)&lt;/code&gt;&lt;/strong&gt;; else start a new block. Used for “total covered time” after merging.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;[1,4)&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;[3,6)&lt;/code&gt;&lt;/strong&gt; merge to &lt;strong&gt;&lt;code&gt;[1,6)&lt;/code&gt;&lt;/strong&gt; (covered length &lt;strong&gt;5&lt;/strong&gt;).&lt;/p&gt;

&lt;h4&gt;
  
  
  Sweep line (concurrency / max overlap)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Expand each interval to &lt;strong&gt;&lt;code&gt;(start, +1)&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;(end, -1)&lt;/code&gt;&lt;/strong&gt;; sort all points; walk while tracking a &lt;strong&gt;running balance&lt;/strong&gt; of active intervals; &lt;strong&gt;&lt;code&gt;max&lt;/code&gt;&lt;/strong&gt; of the balance is the peak concurrency (see sample below).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; Intervals &lt;strong&gt;&lt;code&gt;[0,3)&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;[2,5)&lt;/code&gt;&lt;/strong&gt; — at time &lt;strong&gt;2&lt;/strong&gt; both are active → peak concurrency &lt;strong&gt;2&lt;/strong&gt; (see full solution below).&lt;/p&gt;

&lt;h4&gt;
  
  
  Stateful dict (“stage machine”)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;state[order_id] = (stage, last_ts)&lt;/code&gt;&lt;/strong&gt; (or similar): on each log line, update the entity’s state and maybe accumulate &lt;strong&gt;durations&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;now - last_ts&lt;/code&gt;&lt;/strong&gt; for the previous stage—pattern for marketplace order timelines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Log lines: &lt;strong&gt;&lt;code&gt;order_A placed t=0&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;order_A shipped t=10&lt;/code&gt;&lt;/strong&gt;. After second line, time-in-&lt;strong&gt;placed&lt;/strong&gt; = &lt;strong&gt;10 − 0&lt;/strong&gt;; update &lt;strong&gt;&lt;code&gt;state["order_A"] = ("shipped", 10)&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Sliding windows in memory
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Keep a &lt;strong&gt;deque&lt;/strong&gt; or index of events &lt;strong&gt;within the last N seconds&lt;/strong&gt;; drop expired items as time advances—pattern for “active users in last 5 minutes” style prompts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; Times &lt;strong&gt;&lt;code&gt;100, 250, 400&lt;/code&gt;&lt;/strong&gt; (seconds), window &lt;strong&gt;300s&lt;/strong&gt;. When processing &lt;strong&gt;400&lt;/strong&gt;, drop timestamps &lt;strong&gt;&amp;lt; 100&lt;/strong&gt; → deque holds &lt;strong&gt;&lt;code&gt;[250, 400]&lt;/code&gt;&lt;/strong&gt; (two events in window).&lt;/p&gt;

&lt;h4&gt;
  
  
  Out-of-order events (real pipelines)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Network or collectors may deliver &lt;strong&gt;late&lt;/strong&gt; rows. If the problem says “events can arrive out of order,” you may need to &lt;strong&gt;buffer&lt;/strong&gt; by key until a &lt;strong&gt;watermark&lt;/strong&gt; (e.g. “no more events with &lt;strong&gt;&lt;code&gt;ts &amp;lt; T&lt;/code&gt;&lt;/strong&gt; will arrive”), or &lt;strong&gt;re-sort&lt;/strong&gt; a bounded buffer before updating state.&lt;/li&gt;
&lt;li&gt;Interview &lt;strong&gt;simulations&lt;/strong&gt; usually assume &lt;strong&gt;sorted input&lt;/strong&gt; or &lt;strong&gt;single-threaded&lt;/strong&gt; append—if unsure, &lt;strong&gt;ask&lt;/strong&gt; whether &lt;strong&gt;&lt;code&gt;sort&lt;/code&gt; first&lt;/strong&gt; is allowed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worked example:&lt;/strong&gt; Stream order &lt;strong&gt;&lt;code&gt;ts = 5, 2, 8&lt;/code&gt;&lt;/strong&gt; for the same key. If you &lt;strong&gt;must&lt;/strong&gt; emit “sum so far in time order,” hold rows in a &lt;strong&gt;min-heap by &lt;code&gt;ts&lt;/code&gt;&lt;/strong&gt; until the watermark passes &lt;strong&gt;2&lt;/strong&gt; (e.g. you have seen &lt;strong&gt;&lt;code&gt;ts ≥ 3&lt;/code&gt;&lt;/strong&gt;), then release &lt;strong&gt;2&lt;/strong&gt; before &lt;strong&gt;5&lt;/strong&gt;—or &lt;strong&gt;&lt;code&gt;sort&lt;/code&gt;&lt;/strong&gt; the batch if the problem allows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common beginner mistakes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not &lt;strong&gt;sorting&lt;/strong&gt; by time before simulating.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Closed&lt;/strong&gt; intervals on &lt;strong&gt;both&lt;/strong&gt; ends → accidental &lt;strong&gt;double count&lt;/strong&gt; at shared boundaries.&lt;/li&gt;
&lt;li&gt;Tie-breaking in &lt;strong&gt;&lt;code&gt;events.sort()&lt;/code&gt;&lt;/strong&gt; when start and end times &lt;strong&gt;coincide&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Python Interview Question on Intervals and Maximum Overlap
&lt;/h3&gt;

&lt;p&gt;Events are &lt;code&gt;(start, end)&lt;/code&gt; tuples when a server is busy. Use &lt;strong&gt;half-open&lt;/strong&gt; intervals &lt;code&gt;[start, end)&lt;/code&gt;. Return the &lt;strong&gt;maximum number of overlapping&lt;/strong&gt; busy intervals at any time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimized Solution Using Sweep Line Algorithm
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;max_overlap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intervals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;intervals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# start: +1 concurrent
&lt;/span&gt;        &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# end: -1 concurrent
&lt;/span&gt;    &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt;
        &lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;best&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this works:&lt;/strong&gt; At any time, &lt;code&gt;cur&lt;/code&gt; is how many intervals are &lt;strong&gt;active&lt;/strong&gt;; we record the peak.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practice&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PYTHON · Topic —&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/topic/streaming" rel="noopener noreferrer"&gt;Streaming &amp;amp; interval-style problems&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;COMPANY · Meta — streaming&lt;/strong&gt; &lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/streaming" rel="noopener noreferrer"&gt;Meta-tagged streaming&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tips to Crack Meta Data Engineering Interviews
&lt;/h2&gt;

&lt;p&gt;These tips will help you &lt;strong&gt;confidently crack Meta data engineering interviews&lt;/strong&gt; by focusing on the &lt;strong&gt;technical and problem-solving skills&lt;/strong&gt; interviewers actually score—not textbook definitions. Solid &lt;strong&gt;data engineering interview preparation&lt;/strong&gt; blends &lt;strong&gt;SQL preparation for interviews&lt;/strong&gt; with typed &lt;strong&gt;Python&lt;/strong&gt; practice; &lt;strong&gt;how to crack a data engineering interview&lt;/strong&gt; at this level is mostly &lt;strong&gt;repetition with feedback&lt;/strong&gt;, not passive reading.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Meta data engineer interview tips&lt;/strong&gt; below are &lt;strong&gt;practical&lt;/strong&gt;: habits, patterns, and where to drill on PipeCode. They do &lt;strong&gt;not&lt;/strong&gt; re-explain sections &lt;strong&gt;1–10&lt;/strong&gt;—they tell you &lt;strong&gt;what to do&lt;/strong&gt; with that material.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick checklist (prep habits):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drill &lt;strong&gt;SQL&lt;/strong&gt; daily: joins, &lt;strong&gt;&lt;code&gt;GROUP BY&lt;/code&gt;&lt;/strong&gt;, window functions, dates, nulls, and set-style logic.&lt;/li&gt;
&lt;li&gt;Rehearse &lt;strong&gt;data pipeline and ETL&lt;/strong&gt; thinking: sources, transforms, delivery, and what breaks at scale.&lt;/li&gt;
&lt;li&gt;Be ready to discuss &lt;strong&gt;data-oriented system design&lt;/strong&gt;: schemas, data flow, and reliability—not just a single query.&lt;/li&gt;
&lt;li&gt;Strengthen &lt;strong&gt;Python for data processing&lt;/strong&gt;: dictionaries, counting, streaming, and interval-style problems.&lt;/li&gt;
&lt;li&gt;Work &lt;strong&gt;real problems&lt;/strong&gt; on the &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/company/meta" rel="noopener noreferrer"&gt;Meta company practice hub&lt;/a&gt;&lt;/strong&gt; with tests, not theory alone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SQL Preparation Tips
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Strong SQL&lt;/strong&gt; is non-negotiable. &lt;strong&gt;SQL preparation for interviews&lt;/strong&gt; should emphasize &lt;strong&gt;correctness first&lt;/strong&gt;, then &lt;strong&gt;clarity&lt;/strong&gt;: name the &lt;strong&gt;grain&lt;/strong&gt; (what one row means), say &lt;strong&gt;&lt;code&gt;WHERE&lt;/code&gt; vs &lt;code&gt;HAVING&lt;/code&gt;&lt;/strong&gt;, and sanity-check &lt;strong&gt;join fan-out&lt;/strong&gt; before you optimize. Timed reps beat rereading notes—use &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/aggregation" rel="noopener noreferrer"&gt;Meta · aggregation&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/filtering" rel="noopener noreferrer"&gt;Meta · filtering&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/joins" rel="noopener noreferrer"&gt;Meta · joins&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/window-functions" rel="noopener noreferrer"&gt;Meta · window functions&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/subqueries" rel="noopener noreferrer"&gt;Meta · subqueries&lt;/a&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/cte" rel="noopener noreferrer"&gt;Meta · CTE&lt;/a&gt;&lt;/strong&gt; for company-scoped drills; add &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/topic/null-handling" rel="noopener noreferrer"&gt;Topic · null handling&lt;/a&gt;&lt;/strong&gt; when you practice &lt;strong&gt;NULL-safe&lt;/strong&gt; reporting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Pipeline &amp;amp; ETL Tips
&lt;/h3&gt;

&lt;p&gt;Many &lt;strong&gt;data engineering interview&lt;/strong&gt; loops expect you to &lt;strong&gt;reason about pipelines&lt;/strong&gt;: ingestion, transformation, and serving—often with &lt;strong&gt;ETL&lt;/strong&gt;-style trade-offs (batch vs incremental, idempotency, late data). You do not need a slide deck—&lt;strong&gt;you need vocabulary&lt;/strong&gt;: what is &lt;strong&gt;upstream&lt;/strong&gt;, what is &lt;strong&gt;idempotent&lt;/strong&gt;, what &lt;strong&gt;schema&lt;/strong&gt; does the consumer need? On PipeCode, warm up with &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/etl" rel="noopener noreferrer"&gt;Meta · ETL&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/dimensional-modeling" rel="noopener noreferrer"&gt;Meta · dimensional modeling&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/event-modeling" rel="noopener noreferrer"&gt;Meta · event modeling&lt;/a&gt;&lt;/strong&gt;, and the broader &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/topic/etl" rel="noopener noreferrer"&gt;topic · ETL&lt;/a&gt;&lt;/strong&gt; hub.&lt;/p&gt;

&lt;h3&gt;
  
  
  System Design Preparation Tips
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;System design for data engineering&lt;/strong&gt; is usually &lt;strong&gt;data-centric&lt;/strong&gt;: sketch &lt;strong&gt;components&lt;/strong&gt; (ingest, store, process, serve), &lt;strong&gt;data flow&lt;/strong&gt;, &lt;strong&gt;failure modes&lt;/strong&gt;, and &lt;strong&gt;scale&lt;/strong&gt; (partitioning, backfill, duplicates). If you only prepare &lt;strong&gt;SQL&lt;/strong&gt; in isolation, practice &lt;strong&gt;one whiteboard-style walkthrough&lt;/strong&gt; per week: inputs, outputs, and where &lt;strong&gt;quality&lt;/strong&gt; is enforced. &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/deep-dives" rel="noopener noreferrer"&gt;Deep dives&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/courses" rel="noopener noreferrer"&gt;Explore courses&lt;/a&gt;&lt;/strong&gt; can complement problem reps when you want structured depth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coding &amp;amp; Python Tips
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt; screens favor &lt;strong&gt;clear code&lt;/strong&gt; over clever tricks. Prioritize &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/hash-table" rel="noopener noreferrer"&gt;Meta · hash table&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/topic/hash-table" rel="noopener noreferrer"&gt;topic · hash table&lt;/a&gt;&lt;/strong&gt; for frequency and dict patterns; use &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/streaming" rel="noopener noreferrer"&gt;Meta · streaming&lt;/a&gt;&lt;/strong&gt; with &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/topic/sliding-window" rel="noopener noreferrer"&gt;topic · sliding window&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/topic/intervals" rel="noopener noreferrer"&gt;topic · intervals&lt;/a&gt;&lt;/strong&gt; for window and sweep-line style tasks. State &lt;strong&gt;time and space&lt;/strong&gt; complexity when the interviewer signals they care.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understand Common Interview Patterns
&lt;/h3&gt;

&lt;p&gt;To &lt;strong&gt;crack data engineering interview problems&lt;/strong&gt; faster, &lt;strong&gt;recognize the pattern&lt;/strong&gt; before you code—&lt;strong&gt;aggregation&lt;/strong&gt;, &lt;strong&gt;filtering&lt;/strong&gt;, &lt;strong&gt;joins&lt;/strong&gt;, &lt;strong&gt;windows&lt;/strong&gt;, &lt;strong&gt;CTEs&lt;/strong&gt;, &lt;strong&gt;dates&lt;/strong&gt;, &lt;strong&gt;sets&lt;/strong&gt;, &lt;strong&gt;hash maps&lt;/strong&gt;, &lt;strong&gt;streaming&lt;/strong&gt;. When a prompt feels new, map it to one of those &lt;strong&gt;shapes&lt;/strong&gt; (see sections &lt;strong&gt;1–10&lt;/strong&gt;), then pick the smallest &lt;strong&gt;example&lt;/strong&gt; and trace it by hand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where to practice on PipeCode
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Primary loop:&lt;/strong&gt; &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/company/meta" rel="noopener noreferrer"&gt;Meta company practice hub&lt;/a&gt;&lt;/strong&gt;. &lt;strong&gt;Browse&lt;/strong&gt; &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/topics" rel="noopener noreferrer"&gt;all practice topics&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/companies" rel="noopener noreferrer"&gt;company hubs&lt;/a&gt;&lt;/strong&gt;; full library: &lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice" rel="noopener noreferrer"&gt;Explore practice&lt;/a&gt;&lt;/strong&gt;. &lt;strong&gt;Commitment:&lt;/strong&gt; &lt;strong&gt;&lt;a href="https://pipecode.ai/subscribe" rel="noopener noreferrer"&gt;Subscribe&lt;/a&gt;&lt;/strong&gt; when you want full access.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill lane&lt;/th&gt;
&lt;th&gt;Where to practice on PipeCode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Aggregations &amp;amp; &lt;code&gt;GROUP BY&lt;/code&gt; / &lt;code&gt;HAVING&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/aggregation" rel="noopener noreferrer"&gt;Meta · aggregation&lt;/a&gt;, &lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/grouping" rel="noopener noreferrer"&gt;Meta · grouping&lt;/a&gt;, &lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/having-clause" rel="noopener noreferrer"&gt;Meta · having clause&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filtering&lt;/td&gt;
&lt;td&gt;&lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/filtering" rel="noopener noreferrer"&gt;Meta · filtering&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Joins &amp;amp; dedupe&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/joins" rel="noopener noreferrer"&gt;Meta · joins&lt;/a&gt;, &lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/join" rel="noopener noreferrer"&gt;Meta · join&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows &amp;amp; ranking&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/window-functions" rel="noopener noreferrer"&gt;Meta · window functions&lt;/a&gt;, &lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/ranking" rel="noopener noreferrer"&gt;Meta · ranking&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subqueries &amp;amp; CTEs&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/subqueries" rel="noopener noreferrer"&gt;Meta · subqueries&lt;/a&gt;, &lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/cte" rel="noopener noreferrer"&gt;Meta · CTE&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dates &amp;amp; time-series&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/date-functions" rel="noopener noreferrer"&gt;Meta · date functions&lt;/a&gt;, &lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/time-series" rel="noopener noreferrer"&gt;Meta · time-series&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Set-style logic&lt;/td&gt;
&lt;td&gt;&lt;a href="https://pipecode.ai/explore/practice/company/meta/topic/set" rel="noopener noreferrer"&gt;Meta · set&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Interview habit:&lt;/strong&gt; say &lt;strong&gt;&lt;code&gt;WHERE&lt;/code&gt; vs &lt;code&gt;HAVING&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;join grain&lt;/strong&gt; out loud before you type.&lt;/p&gt;

&lt;h3&gt;
  
  
  Communication under time pressure
&lt;/h3&gt;

&lt;p&gt;State &lt;strong&gt;assumptions&lt;/strong&gt; (grain, nulls, ties), &lt;strong&gt;sketch&lt;/strong&gt; a tiny test case, then code—interviewers reward &lt;strong&gt;data engineering judgment&lt;/strong&gt;, not just syntax.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What questions are asked in Meta data engineering interviews?
&lt;/h3&gt;

&lt;p&gt;Loops often stress &lt;strong&gt;SQL&lt;/strong&gt; (aggregations, joins, windows, dates, nulls, sets) and &lt;strong&gt;Python&lt;/strong&gt; (hash maps, counting, streaming-style intervals). &lt;strong&gt;Exact questions vary&lt;/strong&gt; by team and level; this guide teaches those &lt;strong&gt;topic types&lt;/strong&gt; with &lt;strong&gt;original&lt;/strong&gt; examples aligned to PipeCode’s &lt;strong&gt;Meta&lt;/strong&gt; skill tags.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to prepare for Meta data engineering interview questions?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Read&lt;/strong&gt; the &lt;strong&gt;first teaching subheading&lt;/strong&gt; under each topic (the &lt;strong&gt;SQL&lt;/strong&gt; or &lt;strong&gt;Python&lt;/strong&gt; explainer block), &lt;strong&gt;type&lt;/strong&gt; the sample solutions, then &lt;strong&gt;practice&lt;/strong&gt; on PipeCode’s &lt;a href="https://pipecode.ai/explore/practice/company/meta" rel="noopener noreferrer"&gt;Meta hub&lt;/a&gt;. Usually &lt;strong&gt;SQL first&lt;/strong&gt;, then &lt;strong&gt;Python&lt;/strong&gt;; &lt;strong&gt;450+&lt;/strong&gt; problems are available for reps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are coding questions asked in Meta data engineering interviews?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Yes.&lt;/strong&gt; Many candidates see &lt;strong&gt;SQL&lt;/strong&gt; and &lt;strong&gt;Python&lt;/strong&gt; exercises. This guide matches that mix with &lt;strong&gt;worked examples&lt;/strong&gt; in sections &lt;strong&gt;1–10&lt;/strong&gt;—not a copy of any single live question.&lt;/p&gt;

&lt;h3&gt;
  
  
  What SQL questions are asked in Meta data engineering interviews?
&lt;/h3&gt;

&lt;p&gt;Expect &lt;strong&gt;shapes&lt;/strong&gt; like &lt;strong&gt;aggregation&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;GROUP BY&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;joins&lt;/strong&gt;, &lt;strong&gt;window functions&lt;/strong&gt;, &lt;strong&gt;dates&lt;/strong&gt;, &lt;strong&gt;NULL-safe&lt;/strong&gt; rates, and &lt;strong&gt;set-style&lt;/strong&gt; logic. See &lt;strong&gt;sections 1–8&lt;/strong&gt;; examples are &lt;strong&gt;PostgreSQL-style&lt;/strong&gt; unless noted.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are common data engineering interview problems at Meta?
&lt;/h3&gt;

&lt;p&gt;Common &lt;strong&gt;patterns&lt;/strong&gt; include &lt;strong&gt;summaries by grain&lt;/strong&gt;, &lt;strong&gt;join&lt;/strong&gt; fan-out, &lt;strong&gt;ranking&lt;/strong&gt; and &lt;strong&gt;windows&lt;/strong&gt;, &lt;strong&gt;time buckets&lt;/strong&gt;, &lt;strong&gt;safe percentages&lt;/strong&gt;, and &lt;strong&gt;Python&lt;/strong&gt; frequency or &lt;strong&gt;interval&lt;/strong&gt; sweeps. The &lt;strong&gt;topic table&lt;/strong&gt; near the top lists them.&lt;/p&gt;

&lt;h3&gt;
  
  
  What skills are required to crack Meta data engineering interviews?
&lt;/h3&gt;

&lt;p&gt;Strong &lt;strong&gt;SQL&lt;/strong&gt; (aggregations, joins, windows, dates), solid &lt;strong&gt;Python&lt;/strong&gt; for &lt;strong&gt;dicts&lt;/strong&gt;, &lt;strong&gt;counts&lt;/strong&gt;, and &lt;strong&gt;streaming-style&lt;/strong&gt; logic, plus &lt;strong&gt;clear communication&lt;/strong&gt; under time pressure. &lt;strong&gt;PipeCode&lt;/strong&gt; offers &lt;strong&gt;Meta&lt;/strong&gt;-tagged practice across these topics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start practicing Meta-style data engineering problems
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PipeCode&lt;/strong&gt; pairs &lt;strong&gt;company-tagged&lt;/strong&gt; Meta problems with tests and feedback so you move from reading solutions to &lt;strong&gt;typing your own&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pipecode.ai/" rel="noopener noreferrer"&gt;Pipecode.ai&lt;/a&gt; is Leetcode for Data Engineering&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://pipecode.ai/explore/practice/company/meta" rel="noopener noreferrer"&gt;Browse Meta practice →&lt;/a&gt;&lt;/strong&gt; &lt;strong&gt;&lt;a href="https://pipecode.ai/subscribe" rel="noopener noreferrer"&gt;View plans →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>interview</category>
      <category>python</category>
      <category>sql</category>
    </item>
    <item>
      <title>Why the Best Engineers Are Adopting AI — While Others Are Getting Left Behind</title>
      <dc:creator>Gowtham Potureddi</dc:creator>
      <pubDate>Tue, 14 Apr 2026 06:40:01 +0000</pubDate>
      <link>https://dev.to/gowthampotureddi/why-the-best-engineers-are-adopting-ai-while-others-are-getting-left-behind-mkb</link>
      <guid>https://dev.to/gowthampotureddi/why-the-best-engineers-are-adopting-ai-while-others-are-getting-left-behind-mkb</guid>
      <description>&lt;p&gt;I'm going to be brutally honest about something I see happening across the industry right now. And if you're a senior engineer, this might sting a little — because I've been exactly where you are.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The best engineers I know have already integrated AI into every part of their workflow.&lt;/strong&gt; They're shipping 2-3x faster, producing higher-quality code, and getting promoted ahead of their peers. Meanwhile, another group of equally talented engineers is sitting on the sidelines, watching — and falling behind every single day.&lt;/p&gt;

&lt;p&gt;The difference isn't talent. It's mindset.&lt;/p&gt;

&lt;h2&gt;
  
  
  I Was One of the Resistors
&lt;/h2&gt;

&lt;p&gt;Let me be real: I was the engineer who thought AI was overhyped. I had 7+ years of experience across SAP, Oracle, Reliance, and Experian. I'd built production systems from scratch, architected cloud infrastructure, led teams. Why would I need a tool to "write code for me"?&lt;/p&gt;

&lt;p&gt;That attitude cost me months of productivity I'll never get back.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Changed When I Finally Made the Shift
&lt;/h2&gt;

&lt;p&gt;But once I got over myself and actually committed to using AI properly, the results were staggering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I went from spending 60% of my time on boilerplate to spending &lt;strong&gt;90% of my time on architecture and logic&lt;/strong&gt; — the parts that actually need my brain&lt;/li&gt;
&lt;li&gt;I built and launched &lt;a href="https://pipecode.ai" rel="noopener noreferrer"&gt;two full products&lt;/a&gt; in months, not years — as a 3-person bootstrapped team&lt;/li&gt;
&lt;li&gt;My debugging time dropped from 30-60 minutes per issue to under 5 minutes&lt;/li&gt;
&lt;li&gt;I write 70% less code than I did a year ago — with the same or better output quality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't theoretical improvements. This is my actual, day-to-day reality. And they're the reason I'm writing this post — because too many talented engineers are leaving these gains on the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern I Keep Seeing
&lt;/h2&gt;

&lt;p&gt;I've worked with hundreds of engineers across my career, and the pattern is always the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Senior engineer with a decade of hard-earned experience&lt;/li&gt;
&lt;li&gt;Has heard about ChatGPT, Copilot, Cursor — probably even tried one briefly&lt;/li&gt;
&lt;li&gt;Didn't go deep. Dismissed it as "not ready yet" or "not for serious engineering"&lt;/li&gt;
&lt;li&gt;Meanwhile, a mid-level engineer on the same team adopted AI and started outpacing them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The uncomfortable truth? The senior engineer's resistance isn't about the tools. &lt;strong&gt;It's about identity.&lt;/strong&gt; They've built their career on being the person who &lt;em&gt;knows things&lt;/em&gt;. AI makes it feel like that knowledge is devalued. Using a tool that generates code feels like admitting you need help.&lt;/p&gt;

&lt;p&gt;I know this because I felt it too. And it was completely wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mindset Shift That Changed Everything
&lt;/h2&gt;

&lt;p&gt;Here's what I eventually realized — and what transformed how I work:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI doesn't replace what you know. It multiplies it.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A junior engineer using AI produces junior-quality code faster. The output is quick but still needs heavy review.&lt;/li&gt;
&lt;li&gt;A senior engineer using AI produces &lt;strong&gt;battle-tested, production-ready code&lt;/strong&gt; at 3x speed. Because they know what good architecture looks like, what edge cases to watch for, and which AI suggestions to reject.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your 10 years of experience don't become less valuable with AI. They become &lt;strong&gt;dramatically more valuable&lt;/strong&gt; because you can apply them at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Playbook: How to Make the Shift
&lt;/h2&gt;

&lt;p&gt;If you've been on the fence, here's exactly what I'd do if I were starting today:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start with &lt;a href="https://cursor.sh" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt;&lt;/strong&gt; — it's an AI-native IDE built on VS Code. Don't just use autocomplete. Use the codebase-aware chat (Cmd+L). That's where the real transformation happens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use AI for non-code tasks first&lt;/strong&gt; — emails, documentation, proposals, code reviews. This builds the habit without the ego friction of "AI writing my code."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track your time savings for one week&lt;/strong&gt; — write down what you did with AI and how long it would have taken without. The numbers will shock you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go deeper, not wider&lt;/strong&gt; — master one tool completely before trying five others. Depth beats breadth here.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Window Is Closing
&lt;/h2&gt;

&lt;p&gt;The engineers who adopted AI early aren't just slightly ahead. They're operating at a fundamentally different level. Every month you wait, the gap widens.&lt;/p&gt;

&lt;p&gt;This isn't fear-mongering. This is what I'm living. And I don't want talented engineers to miss this shift the way I almost did.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I share my AI workflows, tool discoveries, and engineering insights every single day on LinkedIn. If you want the daily, unfiltered version — not just the blog posts — that's where it happens.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.linkedin.com/in/potureddi-gowtham/" rel="noopener noreferrer"&gt;Follow me on LinkedIn →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>aiproducts</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Stopped Typing on My Laptop 11 Months Ago — And It Completely Transformed My Productivity</title>
      <dc:creator>Gowtham Potureddi</dc:creator>
      <pubDate>Sun, 12 Apr 2026 08:23:18 +0000</pubDate>
      <link>https://dev.to/gowthampotureddi/i-stopped-typing-on-my-laptop-11-months-ago-and-it-completely-transformed-my-productivity-5fpe</link>
      <guid>https://dev.to/gowthampotureddi/i-stopped-typing-on-my-laptop-11-months-ago-and-it-completely-transformed-my-productivity-5fpe</guid>
      <description>&lt;p&gt;I'm going to share something that has been one of the biggest productivity breakthroughs of my entire engineering career. And no, it's not a fancy AI coding assistant or a new framework. It's something so simple that most engineers completely overlook it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I stopped typing. I started talking. And my output tripled.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;11 months ago, I made a small change to how I work — and it snowballed into a complete transformation of my daily workflow. Emails that took 10 minutes now take 3. Slack messages that I'd agonize over for 5 minutes are done in 30 seconds. Documentation, proposals, even the first draft of this blog post — all spoken, not typed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Productivity Gap Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Here's a fact that changed my perspective forever: the average person types at 40 words per minute. The average person &lt;em&gt;speaks&lt;/em&gt; at 150 words per minute. That's nearly a &lt;strong&gt;4x difference&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every single day, you're operating at 25% of your potential output speed. Think about that. Every email, every Slack message, every code comment, every document — you're leaving 75% of your speed on the table.&lt;/p&gt;

&lt;p&gt;Once I saw this gap, I couldn't unsee it. And once I fixed it, I couldn't go back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Exact Tools I Use (And Why)
&lt;/h2&gt;

&lt;p&gt;After testing over a dozen speech-to-text tools in the past year, these are the ones that actually survived my workflow. I've thrown out the rest.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. SuperWhisper — The Privacy-First Champion
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Price:&lt;/strong&gt; Free&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What it does:&lt;/strong&gt; Runs OpenAI's Whisper model locally on your Mac. Your voice never leaves your machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why I love it:&lt;/strong&gt; As an engineer who handles sensitive codebases and client data, the fact that nothing goes to the cloud is a game-changer. The accuracy is incredible for technical vocabulary too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get it:&lt;/strong&gt; &lt;a href="https://superwhisper.com" rel="noopener noreferrer"&gt;superwhisper.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Built-in Dictation (Mac/Windows)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Price:&lt;/strong&gt; Free (already on your machine right now)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What it does:&lt;/strong&gt; System-level dictation with zero setup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it's useful:&lt;/strong&gt; When you just need to fire off a quick message and don't want to open another tool. It's not the most accurate, but it's instant and frictionless.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Wispr Flow — The AI-Powered Upgrade
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Price:&lt;/strong&gt; Paid (worth every penny)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What it does:&lt;/strong&gt; Goes far beyond basic dictation. It paraphrases, rewrites, understands context, and adapts to the app you're using.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it's next-level:&lt;/strong&gt; I speak in rough, messy thoughts — and Wispr Flow turns them into polished, professional text. It's like having a writing assistant that works at the speed of speech.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get it:&lt;/strong&gt; &lt;a href="https://wisprflow.ai/r?GOWTHAM51" rel="noopener noreferrer"&gt;wisprflow.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Transformation Was Faster Than I Expected
&lt;/h2&gt;

&lt;p&gt;I'll be honest — I was skeptical at first. "Talking to my laptop? That's weird." I thought it would feel awkward, unnatural, and slower than just typing.&lt;/p&gt;

&lt;p&gt;I was completely wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After 3 days&lt;/strong&gt;, I couldn't go back to typing for first drafts. &lt;strong&gt;After 3 weeks&lt;/strong&gt;, I realized I'd been handicapping myself for my entire career. &lt;strong&gt;After 3 months&lt;/strong&gt;, speech-to-text became so natural that typing long messages started feeling painfully slow.&lt;/p&gt;

&lt;p&gt;The shift isn't just about speed — it's about capturing ideas before they vanish. When you type, your brain filters and edits in real-time. You lose thoughts mid-sentence. When you speak, you get the raw, unfiltered stream of consciousness first, and refine later. The quality of my first drafts improved dramatically because I stopped self-editing while creating.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means For Your Workflow
&lt;/h2&gt;

&lt;p&gt;If you write emails, Slack messages, documentation, code reviews, proposals, or any form of text as part of your engineering job — you're leaving hours on the table every single week.&lt;/p&gt;

&lt;p&gt;I conservatively estimate I've saved &lt;strong&gt;15+ hours per month&lt;/strong&gt; since making this switch. That's almost two full working days. Imagine what you could build with two extra days every month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start Today — It Takes 5 Minutes
&lt;/h2&gt;

&lt;p&gt;Don't overthink this. Pick one tool from the list above. Try it for one day. Just one.&lt;/p&gt;

&lt;p&gt;I promise you — once you experience the gap between speaking speed and typing speed, you'll wonder how you ever worked any other way.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I share insights like this every day on my LinkedIn. If you want daily AI + engineering productivity tips that actually move the needle, follow me there — I post things on LinkedIn that I don't always turn into full blog posts.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.linkedin.com/in/potureddi-gowtham/" rel="noopener noreferrer"&gt;Follow me on LinkedIn →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>productivitytools</category>
    </item>
  </channel>
</rss>
