<?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: Sukriti Chatterjee</title>
    <description>The latest articles on DEV Community by Sukriti Chatterjee (@sukriti_c).</description>
    <link>https://dev.to/sukriti_c</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4021742%2Fe898c4ec-f204-4108-b1f0-a97d60252a0a.jpg</url>
      <title>DEV Community: Sukriti Chatterjee</title>
      <link>https://dev.to/sukriti_c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sukriti_c"/>
    <language>en</language>
    <item>
      <title>5 SQL Semantics That Trip Developers Up (And How to Fix Them)</title>
      <dc:creator>Sukriti Chatterjee</dc:creator>
      <pubDate>Sat, 08 Aug 2026 04:30:00 +0000</pubDate>
      <link>https://dev.to/sukriti_c/5-sql-semantics-that-trip-developers-up-and-how-to-fix-them-3a3j</link>
      <guid>https://dev.to/sukriti_c/5-sql-semantics-that-trip-developers-up-and-how-to-fix-them-3a3j</guid>
      <description>&lt;p&gt;Most developers learn SQL by writing basic &lt;code&gt;SELECT&lt;/code&gt; statements until the red squiggle goes away. But SQL isn't a standard procedural language like Python, TypeScript, or Go—it is a &lt;strong&gt;declarative language based on relational algebra&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Because of this fundamental difference, it is alarmingly easy to write a query that looks logically sound, executes without throwing a single error, but silently produces completely wrong data or tanks your production database performance.&lt;/p&gt;

&lt;p&gt;Here is a breakdown of 5 counter-intuitive SQL semantics that trip developers up every single day, along with how the database engine actually processes them.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. &lt;code&gt;NULL = NULL&lt;/code&gt; is NOT True (Three-Valued Logic)
&lt;/h2&gt;

&lt;p&gt;In almost every traditional programming language, equality is binary and reflexive: &lt;code&gt;x == x&lt;/code&gt; evaluates to &lt;code&gt;TRUE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In SQL, this statement returns &lt;strong&gt;0 rows&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="c1"&gt;-- ❌ WRONG: Always returns an empty set&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;middle_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What's Actually Happening?
&lt;/h3&gt;

&lt;p&gt;SQL does not operate on standard two-valued boolean logic (&lt;code&gt;TRUE&lt;/code&gt; / &lt;code&gt;FALSE&lt;/code&gt;). It uses &lt;strong&gt;Three-Valued Logic&lt;/strong&gt;: &lt;code&gt;TRUE&lt;/code&gt;, &lt;code&gt;FALSE&lt;/code&gt;, and &lt;code&gt;UNKNOWN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In SQL, &lt;code&gt;NULL&lt;/code&gt; does not mean zero or an empty string—it represents an &lt;strong&gt;unknown or missing value&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If Person A's middle name is unknown (&lt;code&gt;NULL&lt;/code&gt;), and Person B's middle name is unknown (&lt;code&gt;NULL&lt;/code&gt;), are their middle names equal? The database engine doesn't know! Therefore, &lt;code&gt;NULL = NULL&lt;/code&gt; evaluates to &lt;code&gt;UNKNOWN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When a &lt;code&gt;WHERE&lt;/code&gt; clause evaluates a row, it &lt;strong&gt;only keeps records that resolve strictly to `TRUE&lt;/strong&gt;&lt;code&gt;. &lt;/code&gt;UNKNOWN` is dropped.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Always use explicit null-checking operators:&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="c1"&gt;-- ✅ CORRECT: Explicitly checks for missing values&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;middle_name&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. &lt;code&gt;WHERE&lt;/code&gt; vs &lt;code&gt;HAVING&lt;/code&gt;: The Filter Order Nobody Explains Right
&lt;/h2&gt;

&lt;p&gt;Junior developers often think &lt;code&gt;WHERE&lt;/code&gt; and &lt;code&gt;HAVING&lt;/code&gt; are interchangeable filters, with &lt;code&gt;HAVING&lt;/code&gt; just being "the one you use with &lt;code&gt;GROUP BY&lt;/code&gt;."&lt;/p&gt;

&lt;p&gt;Using them interchangeably can cause massive query latency in production:&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="c1"&gt;-- ❌ UNOPTIMIZED: Filtering raw rows INSIDE HAVING&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;department_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;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;department_id&lt;/span&gt; 
&lt;span class="k"&gt;HAVING&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;'ACTIVE'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What's Actually Happening?
&lt;/h3&gt;

&lt;p&gt;SQL clauses do not execute top-to-bottom in the order they are written. The logical query execution pipeline runs in this sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;FROM&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WHERE&lt;/code&gt; &lt;em&gt;(Filters raw rows on disk)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GROUP BY&lt;/code&gt; &lt;em&gt;(Groups remaining rows)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HAVING&lt;/code&gt; &lt;em&gt;(Filters aggregated groups)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SELECT&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When you put a non-aggregate filter inside &lt;code&gt;HAVING&lt;/code&gt;, you force the database engine to group &lt;em&gt;every single row in disk memory&lt;/em&gt;, compute the aggregations, and &lt;strong&gt;then&lt;/strong&gt; throw away the inactive departments.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Filter raw records as early as possible with &lt;code&gt;WHERE&lt;/code&gt; so your engine groups a much smaller dataset:&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="c1"&gt;-- ✅ OPTIMIZED: Filter FIRST, aggregate SECOND&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;department_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;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&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;'ACTIVE'&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;department_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Golden Rule:&lt;/strong&gt; Use &lt;code&gt;WHERE&lt;/code&gt; to drop raw records before grouping. Reserve &lt;code&gt;HAVING&lt;/code&gt; strictly for aggregate function conditions like &lt;code&gt;COUNT() &amp;gt; 5&lt;/code&gt; or &lt;code&gt;SUM(total) &amp;gt; 1000&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  3. &lt;code&gt;JOIN&lt;/code&gt; vs Subquery: Same Result, Wildly Different Performance
&lt;/h2&gt;

&lt;p&gt;You will often hear developers claim that subqueries and &lt;code&gt;JOIN&lt;/code&gt;s perform identically because modern query optimizers flatten them. While that holds true for simple subqueries, assuming it for &lt;strong&gt;correlated subqueries&lt;/strong&gt; is dangerous.&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="c1"&gt;-- ⚠️ DANGEROUS: Correlated Subquery&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;u&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;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;IN&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;o&lt;/span&gt;&lt;span class="p"&gt;.&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;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; 
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What's Actually Happening?
&lt;/h3&gt;

&lt;p&gt;A correlated subquery references columns from the outer query. If the query optimizer fails to flatten it, the database engine executes the inner subquery &lt;strong&gt;once for every single row in the outer table&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If your &lt;code&gt;users&lt;/code&gt; table has 100,000 rows, that subquery might run 100,000 separate times! This transforms a linear $O(N)$ set-based operation into an $O(N^2)$ nested-loop bottleneck.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Refactor correlated subqueries into set-based &lt;code&gt;JOIN&lt;/code&gt; operations:&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="c1"&gt;-- ✅ OPTIMIZED: Set-based Hash/Loop Join&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;u&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;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; 
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. &lt;code&gt;GROUP BY&lt;/code&gt; vs &lt;code&gt;DISTINCT&lt;/code&gt;: Not Interchangeable
&lt;/h2&gt;

&lt;p&gt;When developers want unique records, they often choose between &lt;code&gt;DISTINCT&lt;/code&gt; and &lt;code&gt;GROUP BY&lt;/code&gt; based on personal syntax preference.&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="c1"&gt;-- ❌ HEAVY OVERHEAD: Using aggregation memory for simple deduplication&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;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="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What's Actually Happening?
&lt;/h3&gt;

&lt;p&gt;Under the hood, these two operations tell the query engine to perform completely different tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DISTINCT&lt;/code&gt; is a &lt;strong&gt;set operation&lt;/strong&gt;. It tells the engine to sort or hash the final dataset and remove duplicate rows.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GROUP BY&lt;/code&gt; is an &lt;strong&gt;aggregation pipeline&lt;/strong&gt;. It allocates memory buckets to prepare for mathematical metrics (&lt;code&gt;SUM&lt;/code&gt;, &lt;code&gt;COUNT&lt;/code&gt;, &lt;code&gt;AVG&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you use &lt;code&gt;GROUP BY&lt;/code&gt; without using an aggregate function, you are forcing the database engine to allocate bucket memory for calculations you never intend to run.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- ✅ CLEAN &amp;amp; EFFICIENT: Direct unique set extraction&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&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;orders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule of Thumb:&lt;/strong&gt; If you aren't calculating metrics across grouped records, do not touch &lt;code&gt;GROUP BY&lt;/code&gt;. Use &lt;code&gt;DISTINCT&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5. &lt;code&gt;LEFT JOIN&lt;/code&gt; + &lt;code&gt;WHERE&lt;/code&gt; = Silently Becomes an &lt;code&gt;INNER JOIN&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This is the single most common silent bug in data engineering.&lt;/p&gt;

&lt;p&gt;Imagine you want a report of &lt;strong&gt;all users&lt;/strong&gt;, including those who have never placed an order:&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="c1"&gt;-- ❌ SILENT BUG: The LEFT JOIN is destroyed by the WHERE clause&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;u&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;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_date&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; 
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&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;'COMPLETED'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What's Actually Happening?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;LEFT JOIN&lt;/code&gt; runs correctly and includes users without orders, padding their order columns with &lt;code&gt;NULL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Next, the &lt;code&gt;WHERE&lt;/code&gt; clause runs to evaluate &lt;code&gt;WHERE o.status = 'COMPLETED'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For a user with zero orders, &lt;code&gt;o.status&lt;/code&gt; is &lt;code&gt;NULL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The expression &lt;code&gt;NULL = 'COMPLETED'&lt;/code&gt; evaluates to &lt;code&gt;UNKNOWN&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;As we learned in Topic 1, &lt;code&gt;UNKNOWN&lt;/code&gt; rows are filtered out!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By adding a &lt;code&gt;WHERE&lt;/code&gt; condition on the right-hand table, you accidentally wiped out all the &lt;code&gt;NULL&lt;/code&gt;-padded rows, &lt;strong&gt;silently converting your &lt;code&gt;LEFT JOIN&lt;/code&gt; into an `INNER JOIN&lt;/strong&gt;`.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Move right-table conditions directly into the &lt;code&gt;ON&lt;/code&gt; clause of the join:&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="c1"&gt;-- ✅ CORRECT: Keeps unmatched users while filtering order criteria&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;u&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;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_date&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; 
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&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;'COMPLETED'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary Checklist for Code Reviews
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Checking for NULLs?&lt;/strong&gt; Use &lt;code&gt;IS NULL&lt;/code&gt; or &lt;code&gt;IS NOT NULL&lt;/code&gt;, never &lt;code&gt;= NULL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filtering rows before grouping?&lt;/strong&gt; Put the condition in &lt;code&gt;WHERE&lt;/code&gt;, not &lt;code&gt;HAVING&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Running subqueries in loops?&lt;/strong&gt; Refactor correlated subqueries to explicit &lt;code&gt;JOIN&lt;/code&gt;s.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deduplicating simple rows?&lt;/strong&gt; Use &lt;code&gt;DISTINCT&lt;/code&gt; instead of &lt;code&gt;GROUP BY&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filtering a &lt;code&gt;LEFT JOIN&lt;/code&gt;?&lt;/strong&gt; Put right-table predicates in the &lt;code&gt;ON&lt;/code&gt; clause to preserve outer rows.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Which of these SQL edge cases has tripped you up in production before? Drop your thoughts or worst database horror stories in the comments below! 🚀&lt;/p&gt;

</description>
      <category>sql</category>
      <category>postgres</category>
      <category>database</category>
      <category>postgressql</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Sukriti Chatterjee</dc:creator>
      <pubDate>Mon, 13 Jul 2026 15:21:06 +0000</pubDate>
      <link>https://dev.to/sukriti_c/-5149</link>
      <guid>https://dev.to/sukriti_c/-5149</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/sukriti_c/you-dont-need-a-smarter-robot-you-need-a-better-assembly-line-3bck" class="crayons-story__hidden-navigation-link"&gt;You Don’t Need a Smarter Robot—You Need a Better Assembly Line&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/sukriti_c" class="crayons-avatar  crayons-avatar--l  "&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4021742%2Fe898c4ec-f204-4108-b1f0-a97d60252a0a.jpg" alt="sukriti_c profile" class="crayons-avatar__image" width="96" height="96"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/sukriti_c" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Sukriti Chatterjee
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Sukriti Chatterjee
                
              
              &lt;div id="story-author-preview-content-4123542" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/sukriti_c" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4021742%2Fe898c4ec-f204-4108-b1f0-a97d60252a0a.jpg" class="crayons-avatar__image" alt="" width="96" height="96"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Sukriti Chatterjee&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/sukriti_c/you-dont-need-a-smarter-robot-you-need-a-better-assembly-line-3bck" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 12&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/sukriti_c/you-dont-need-a-smarter-robot-you-need-a-better-assembly-line-3bck" id="article-link-4123542"&gt;
          You Don’t Need a Smarter Robot—You Need a Better Assembly Line
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/machinelearning"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;machinelearning&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/api"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;api&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/architecture"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;architecture&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/sukriti_c/you-dont-need-a-smarter-robot-you-need-a-better-assembly-line-3bck" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/sukriti_c/you-dont-need-a-smarter-robot-you-need-a-better-assembly-line-3bck#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>You Don’t Need a Smarter Robot—You Need a Better Assembly Line</title>
      <dc:creator>Sukriti Chatterjee</dc:creator>
      <pubDate>Sun, 12 Jul 2026 18:30:00 +0000</pubDate>
      <link>https://dev.to/sukriti_c/you-dont-need-a-smarter-robot-you-need-a-better-assembly-line-3bck</link>
      <guid>https://dev.to/sukriti_c/you-dont-need-a-smarter-robot-you-need-a-better-assembly-line-3bck</guid>
      <description>&lt;h1&gt;
  
  
  Rethinking Book Recommendations with &lt;a href="https://tbrly-app.vercel.app/" rel="noopener noreferrer"&gt;TBRly&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Go to any tech meetup, scroll through your developer timeline, or open your inbox, and you will hear variations of the same hyper-optimistic narrative: &lt;em&gt;“Why build a traditional app anymore? Anyone can just replace an entire software layer with a raw ChatGPT prompt or a customized system instruction.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It is a seductive assumption. It implies that natural language is the ultimate user interface and that the raw reasoning power of Large Language Models (LLMs) has made deterministic software architecture obsolete.&lt;/p&gt;

&lt;p&gt;But if you actually try to live inside that assumption, the reality falls apart instantly. Raw LLMs possess immense cognitive flexibility, but as an interface for consistent, daily operations, they represent a massive step backwards in user experience.&lt;/p&gt;

&lt;p&gt;When you ask a raw AI chatbot like ChatGPT to recommend what you should read next, you are hiring a &lt;strong&gt;brilliant, hyper-intelligent robot&lt;/strong&gt;, handing it a chaotic mental pile of 100 books you might want to read, and expecting it to perfectly organise your brain.&lt;/p&gt;

&lt;p&gt;The problem is that the robot doesn't know your library, it forgets what you said ten prompts ago, and it answers by dumping a massive, exhausting wall of text back into your lap. You haven't solved your decision paralysis; you’ve just turned it into a reading assignment.&lt;/p&gt;

&lt;p&gt;You don't need a smarter robot. &lt;strong&gt;You need a better assembly line.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The future of software isn't just wrapping an API key in a text box and calling it a platform. The value is shifting toward &lt;strong&gt;Workflow Engineering&lt;/strong&gt;—building the deterministic scaffolding, state routing, and silent automation that makes AI actually usable at scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Chaos of the Open Prompt: A UX Nightmare
&lt;/h2&gt;

&lt;p&gt;To understand why raw chat loops fail, let's look at the concrete human problem of managing a massive, chaotic backlog of 100+ unread books scattered across physical shelves, Kindle lists, and browser tabs.&lt;/p&gt;

&lt;p&gt;Imagine trying to manage and curate this personal library entirely inside a standard, raw chat interface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Data Ingestion Wall:&lt;/strong&gt; Your first task is manual data entry. You have to sit down and type out 100 titles, authors, genres, and purchase dates. If you miss a comma or misspell an ambiguous title, the model might infer the wrong edition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Memory Leak:&lt;/strong&gt; LLMs are bounded by context windows. As you interact with your library over weeks—asking it to prioritize choices, filter by mood, or log read status—your historical states begin to drift. The moment your chat thread gets too long, older entries slide out of active memory, causing the system to quietly lose track of books you input on day one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Content Paradigm:&lt;/strong&gt; An LLM treats your request as a text generation problem. If you ask for a recommendation, it will return a massive wall of markdown prose. It forces you to read through paragraphs of conversational filler just to extract a single title.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision Paralysis Redux:&lt;/strong&gt; Ask a chatbot for a reading recommendation, and it will happily give you a static bulleted list of ten books. You are right back where you started: staring at a list, paralysed by multi-variable choice data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A raw chat prompt is high-friction, non-deterministic, and structurally exhausting for the end user. It isn’t an application; it’s an open-ended puzzle.&lt;/p&gt;




&lt;h2&gt;
  
  
  Moving Beyond the Wrapper: Enter Workflow Engineering
&lt;/h2&gt;

&lt;p&gt;True user retention isn’t driven by how powerful your underlying foundational model is. It is driven by &lt;strong&gt;operational mechanics&lt;/strong&gt;—how cleanly your surrounding system architecture abstracts the friction of data entry and structures context routing before the model ever sees it.&lt;/p&gt;

&lt;p&gt;When we designed &lt;strong&gt;&lt;a href="https://tbrly-app.vercel.app/" rel="noopener noreferrer"&gt;TBRly&lt;/a&gt;&lt;/strong&gt;, we stopped treating book curation as a chat game and started treating it as a structured data pipeline. Here is how the book recommendation assembly line works under the hood:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fchyjw9e9ccj5pd7urnym.jpeg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fchyjw9e9ccj5pd7urnym.jpeg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Frictionless Intake
&lt;/h3&gt;

&lt;p&gt;Instead of forcing users to type out their massive reading list line-by-line, you drop raw data—like a quick photo of your bookshelf or a messy search query—directly onto the conveyor belt.&lt;/p&gt;

&lt;p&gt;The pipeline automatically extracts the text, fetches the book covers, hooks into open-source metadata hubs like the Internet Archive's Open Library API, and formats the data into a clean, structured asset array containing covers, genres, and unique identifiers. The user does zero typing; the structural data pipeline handles the logistics.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Quiet Inspector (Context Routing)
&lt;/h3&gt;

&lt;p&gt;When raw strings enter the platform, they don't go straight to an open prompt. Instead, Google AI acts as a quiet supervisor on the assembly line—instantly parsing book titles, cleaning up title typos, generating cross-lingual contextual metadata tags, and applying semantic tags behind the scenes.&lt;/p&gt;

&lt;p&gt;Because this data is structured deterministically into low-latency storage layers like Firestore, the system maintains absolute data fidelity over time. There is no context drift, no lost entries, and no memory leaks.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Sorting Engine ("The Arena")
&lt;/h3&gt;

&lt;p&gt;Your books are automatically pushed off the conveyor belt and funneled into &lt;strong&gt;The Arena&lt;/strong&gt;—a custom, deterministic binary-choice orchestration engine.&lt;/p&gt;

&lt;p&gt;Instead of generating a long conversational list of recommendations, the engine systematically structures your backlog into rapid-fire, head-to-head tournament matchups. By forcing simple binary decisions (Book A vs. Book B) and tracking the state transitions via an optimized frequency hash map in $O(1)$ lookup time, the system mathematically strips away multi-variable decision paralysis.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Value Shift in the AI Era
&lt;/h2&gt;

&lt;p&gt;The tech ecosystem is undergoing a massive architectural realization. The novelty of the raw API call is wearing off. Users do not want to become prompt engineers just to organize their reading data, and they do not want to read conversational essays just to pick their next book.&lt;/p&gt;

&lt;p&gt;The magic of a modern book app shouldn't be a blank chat box that forces the user to do the structural thinking. It should be an invisible assembly line that handles the messy logistics, leaving the human with nothing left to do but open the first page.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>api</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Bypassing Orchestration Frameworks: Building a Lean AI Book Curator Natively on Gemini</title>
      <dc:creator>Sukriti Chatterjee</dc:creator>
      <pubDate>Thu, 09 Jul 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/sukriti_c/bypassing-orchestration-frameworks-building-a-lean-ai-book-curator-natively-on-gemini-1fe8</link>
      <guid>https://dev.to/sukriti_c/bypassing-orchestration-frameworks-building-a-lean-ai-book-curator-natively-on-gemini-1fe8</guid>
      <description>&lt;p&gt;Like most developers with a growing stack of unread physical books, I suffer from a bad case of choice paralysis. Your bookshelf looks at you, you look back at it, and you end up scrolling on your phone instead of reading. &lt;/p&gt;

&lt;p&gt;To solve this, I built &lt;strong&gt;&lt;a href="http://tbrly-app.vercel.app/" rel="noopener noreferrer"&gt;TBRly&lt;/a&gt;&lt;/strong&gt;—a lightweight web application that allows users to snap a photo of their physical bookshelf, instantly parse the titles, and drop them into a "Deathmatch Arena" where a native AI pipeline forces a choice based on your current vibe.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnc0vz0bohlhi2g0tebqq.jpeg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnc0vz0bohlhi2g0tebqq.jpeg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But this isn't a post about marketing a book tool. It’s a post about why I stripped out the abstraction layers and built an AI engine natively.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture Pitfall: The Framework Trap
&lt;/h2&gt;

&lt;p&gt;When I started mapping out the backend, the default path seemed obvious: grab an AI orchestration framework, chain a few prompts together, and deploy. &lt;/p&gt;

&lt;p&gt;But as a software engineer who values performance and predictability, I ran into immediate friction. Standard frameworks add significant middleware overhead, hide the raw payload structures, and make debugging non-deterministic API behaviors a black box. &lt;/p&gt;

&lt;p&gt;For a consumer utility application where latency and snappy rendering matter, I didn't want heavy dependencies. I wanted raw, blazing-fast speed and total control over the context window.&lt;/p&gt;

&lt;p&gt;So, I built native abstractions directly over the Google GenAI SDK.&lt;/p&gt;




&lt;h2&gt;
  
  
  Inside the Core Engine
&lt;/h2&gt;

&lt;p&gt;TBRly relies on two critical technical pillars to take a messy physical reality and turn it into an actionable decision:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Vision parsing without heavy OCR pipelines
&lt;/h3&gt;

&lt;p&gt;Instead of running an expensive, multi-stage pipeline that extracts raw bounding boxes, matches text fragments, and pipes strings to a separate model, TBRly passes the user’s phone image directly to a multimodal model. &lt;/p&gt;

&lt;p&gt;By utilizing strict structured JSON outputs, the model receives the raw pixel buffer and returns a clean, validated array of objects containing only &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;author&lt;/code&gt;. If the image is blurry, it flags it immediately without wasting downstream compute cycles.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Vibe Prompting Architecture
&lt;/h3&gt;

&lt;p&gt;Standard category matching (e.g., "Sci-Fi" vs "History") is boring. TBRly introduces a feature called &lt;strong&gt;Vibe Prompting&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Instead of searching indexed metadata tags, users can input abstract text strings like &lt;em&gt;“Give me a cozy, rainy-day psychological mystery”&lt;/em&gt; or &lt;em&gt;“A fast-paced, mind-bending cyber thriller.”&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;The backend dynamically wraps this intent into an isolated context block, injects the user's parsed bookshelf list, and runs an internal selection matrix natively to output the final matched book and a single, punchy justification string.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Frontend:&lt;/strong&gt; Next.js (optimized edge execution for fast client rendering)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Backend:&lt;/strong&gt; FastAPI (async routing to handle asynchronous API streaming seamlessly)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;AI Layer:&lt;/strong&gt; Native Gemini integration via Google GenAI SDK (zero third-party wrapper lag)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Lessons from the Trenches
&lt;/h2&gt;

&lt;p&gt;Building in public alone means running into immediate structural hurdles. Here is what this project taught me so far:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Data Gravity is Real:&lt;/strong&gt; When handling user images and state, keeping the compute payload close to the model endpoint is the difference between a 4-second loading spinner and a sub-second response.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Lean is Resilient:&lt;/strong&gt; Bypassing massive middleware wrappers forced me to explicitly write my own error-handling logic for rate limits and context safety filters. The result? A highly deterministic codebase that doesn't break when a wrapper package updates its minor version.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The platform is live, 90% free, and currently navigating its early alpha stages. &lt;a href="http://tbrly-app.vercel.app/" rel="noopener noreferrer"&gt;TBRly App&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are an engineer who loves reading or someone who wants to tear apart my architectural choices, I’d love your feedback. How are you handling native LLM abstractions in your production side-projects? Let's discuss below!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>startup</category>
    </item>
  </channel>
</rss>
