<?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: Serhan Yavçin</title>
    <description>The latest articles on DEV Community by Serhan Yavçin (@serhanyavcin).</description>
    <link>https://dev.to/serhanyavcin</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%2F4237854%2F3b22282f-b547-48fe-875a-9deab128c08e.jpg</url>
      <title>DEV Community: Serhan Yavçin</title>
      <link>https://dev.to/serhanyavcin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/serhanyavcin"/>
    <language>en</language>
    <item>
      <title>Your prompt A/B test is lying to you (if you keep checking it)</title>
      <dc:creator>Serhan Yavçin</dc:creator>
      <pubDate>Wed, 11 Nov 2026 12:26:56 +0000</pubDate>
      <link>https://dev.to/serhanyavcin/your-prompt-ab-test-is-lying-to-you-if-you-keep-checking-it-4c1l</link>
      <guid>https://dev.to/serhanyavcin/your-prompt-ab-test-is-lying-to-you-if-you-keep-checking-it-4c1l</guid>
      <description>&lt;p&gt;You rewrote the support prompt. Two days later the dashboard says the new version resolves four points more tickets, and the 95% interval doesn't touch zero. You ship it. A month later the resolution rate is exactly where it was.&lt;/p&gt;

&lt;p&gt;The arithmetic was fine. What went wrong was &lt;em&gt;when&lt;/em&gt; it was read.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a condensed version of a longer guide, with an interactive version of the demo below, sample sizes and a FAQ: &lt;strong&gt;&lt;a href="https://prompthelm.app/guides/ab-testing-llm-prompts?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=ab-testing-guide&amp;amp;utm_content=intro" rel="noopener noreferrer"&gt;A/B testing LLM prompts without false winners&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A 95% interval makes one promise
&lt;/h2&gt;

&lt;p&gt;When there's no real difference, a 95% interval excludes zero only 5% of the time. That promise holds for &lt;strong&gt;one look, at a sample size you fixed in advance&lt;/strong&gt;. A live dashboard recomputes the interval after every batch of results, and every look is a fresh chance for noise to cross the line.&lt;/p&gt;

&lt;p&gt;How much does that cost? We simulated 4,000 A/A tests, where both versions are the same prompt at 30% success, and looked after every 100 outcomes per version. A test "found" a difference if its interval ever excluded zero:&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%2Fqzqifoi0kltnfa48cnbd.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqzqifoi0kltnfa48cnbd.png" alt="Bar chart of 4,000 simulated A/A tests: with a fixed 95% interval checked at every look, 4.4% showed a difference at 1 look, 13.7% at 5, 19.1% at 10, 28.4% at 30 and 38.0% at 100; with an always-valid interval, 0.1% to 2.2%" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Looks&lt;/th&gt;
&lt;th&gt;Fixed 95% interval&lt;/th&gt;
&lt;th&gt;Always-valid 95% interval&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;4.4%&lt;/td&gt;
&lt;td&gt;0.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;13.7%&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;19.1%&lt;/td&gt;
&lt;td&gt;0.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;38.0%&lt;/td&gt;
&lt;td&gt;2.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ten peeks, and one test in five finds a winner that isn't there. Keep looking and it never stops climbing; Armitage, McPherson and Rowe measured the same effect in clinical trials back in 1969.&lt;/p&gt;

&lt;p&gt;Prompt tests invite it more than most. The dashboard is live, and the new prompt was written to win.&lt;/p&gt;

&lt;h2&gt;
  
  
  What peeking looks like
&lt;/h2&gt;

&lt;p&gt;Here is one of those simulated A/A runs. The black line is the new version's lead, the brown lines are the usual 95% interval, and the blue band is an always-valid interval:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw6kwphlvc7rzyqi4bdx0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw6kwphlvc7rzyqi4bdx0.gif" alt="Animated chart of a simulated A/A test: the usual 95% interval calls the new prompt a winner by about four points between 1,500 and 2,200 outcomes, then the lead fades, while the always-valid interval keeps zero inside throughout" width="800" height="579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Between 1,500 and 2,200 outcomes per version, the usual interval says the new prompt wins by about four points, at seven different looks. Then the lead fades, as noise does. The blue band never leaves zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intervals built to be looked at
&lt;/h2&gt;

&lt;p&gt;The fix is the mixture sequential probability ratio test (mSPRT). Instead of asking "is the difference non-zero at this sample size?", it keeps a running likelihood ratio: how much more likely the data are under some real difference than under none, averaged over the differences you'd plausibly see. When there's no difference, that ratio is a martingale, so the chance that it &lt;em&gt;ever&lt;/em&gt; climbs above 1/α is at most α, however often you check (Robbins, 1970). Johari, Koomen, Pekelis and Walsh turned this into p-values and intervals for A/B tests (2022).&lt;/p&gt;

&lt;p&gt;With a normal approximation, the 95% always-valid interval for the difference is &lt;code&gt;diff ± r&lt;/code&gt;, where:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;r = √( V·(V + τ²)/τ² · [ 2·ln(1/α) + ln((V + τ²)/V) ] )
V = s²(A)/n(A) + s²(B)/n(B)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In code, that's one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Half-width of an always-valid interval for mean(B) − mean(A).&lt;/span&gt;
&lt;span class="c1"&gt;// v: the variance of that difference, s²(A)/n(A) + s²(B)/n(B)&lt;/span&gt;
&lt;span class="c1"&gt;// tau: the smallest difference that matters, e.g. 0.05 on a 30% rate&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tau&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;t2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tau&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;tau&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;t2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;t2&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;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;t2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;τ&lt;/code&gt; sets the size of difference the test is most sensitive to, so set it to the smallest one you care about.&lt;/p&gt;

&lt;p&gt;The price is data. For a real lift from 30% to 35%, it found the difference after a median of 1,400 outcomes per version, and by 2,400 in 80% of tests. A fixed test sized for 80% power needs about 1,400, but only if you look once. Roughly, 1.7 times the data buys the right to look whenever you like and to stop as soon as the answer is clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two more ways a prompt test lies
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Randomizing by call.&lt;/strong&gt; A user who gets the formal prompt for one reply and the casual one for the next has a worse experience than either would give, and their outcomes aren't independent anyway. Hash the user or conversation id with the test's id into a bucket from 0 to 99, and serve the new version to buckets below its share. Same user, same version, no table of assignments, and raising the share only moves users from live to new.&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%2Fu33tcy3eep03a05ginke.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu33tcy3eep03a05ginke.png" alt="Flow of one call through a prompt A/B test: the call arrives for a unit, the unit is hashed into a bucket from 0 to 99, buckets below 20 get the new version and the rest get live, the outcome is reported, totals are added up per version, and an always-valid interval with a split check decides" width="800" height="802"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A broken split.&lt;/strong&gt; You asked for 50/50 and got 10,300 calls on live and 9,700 on the new version? A chi-square test gives χ² = 18 and p ≈ 0.00002: the comparison isn't fair, whatever the metric says. That's a sample ratio mismatch (Fabijan et al., 2019), and in LLM apps the usual suspects are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the new prompt is longer and times out more, and calls that time out never report an outcome;&lt;/li&gt;
&lt;li&gt;an older deployment doesn't know about the test and sends every call to live;&lt;/li&gt;
&lt;li&gt;calls missing a variable the new prompt needs fall back to live and are counted there.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check the split continuously with a strict threshold, such as p &amp;lt; 0.001, and stop reading the metric when it trips.&lt;/p&gt;

&lt;h2&gt;
  
  
  A checklist for your next prompt test
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Randomize by user or conversation, by hash, and keep the share steady.&lt;/li&gt;
&lt;li&gt;One primary metric, chosen before the test; tokens and latency as guardrails.&lt;/li&gt;
&lt;li&gt;Write down the smallest difference that matters.&lt;/li&gt;
&lt;li&gt;Read an always-valid interval, or fix the sample size and look once.&lt;/li&gt;
&lt;li&gt;Check the split all the time.&lt;/li&gt;
&lt;li&gt;Let the test stop itself on clear harm; ship by hand, after reading real answers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://prompthelm.app/guides/ab-testing-llm-prompts?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=ab-testing-guide&amp;amp;utm_content=outro" rel="noopener noreferrer"&gt;full guide&lt;/a&gt; goes further: choosing the metric, how many outcomes each effect size needs, changing the share mid-test, the winner's curse, and a FAQ on t-tests, bandits and model-graded scores.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;We build &lt;a href="https://prompthelm.app/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=ab-testing-guide&amp;amp;utm_content=cta" rel="noopener noreferrer"&gt;PromptHelm&lt;/a&gt;, which lets you change the prompts your app sends to an LLM without a redeploy. Its A/B tests work the way this post describes, and the simulations above ran on its statistics code.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Armitage, McPherson &amp;amp; Rowe (1969), &lt;a href="https://doi.org/10.2307/2343787" rel="noopener noreferrer"&gt;Repeated significance tests on accumulating data&lt;/a&gt;, &lt;em&gt;Journal of the Royal Statistical Society, Series A&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Robbins (1970), &lt;a href="https://doi.org/10.1214/aoms/1177696786" rel="noopener noreferrer"&gt;Statistical methods related to the law of the iterated logarithm&lt;/a&gt;, &lt;em&gt;The Annals of Mathematical Statistics&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Johari, Koomen, Pekelis &amp;amp; Walsh (2022), &lt;a href="https://doi.org/10.1287/opre.2021.2135" rel="noopener noreferrer"&gt;Always valid inference: continuous monitoring of A/B tests&lt;/a&gt;, &lt;em&gt;Operations Research&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Fabijan et al. (2019), &lt;a href="https://doi.org/10.1145/3292500.3330722" rel="noopener noreferrer"&gt;Diagnosing sample ratio mismatch in online controlled experiments&lt;/a&gt;, &lt;em&gt;KDD&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>testing</category>
      <category>datascience</category>
    </item>
  </channel>
</rss>
