<?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: Baneet Singh</title>
    <description>The latest articles on DEV Community by Baneet Singh (@baneet_singh_1cd12e795b68).</description>
    <link>https://dev.to/baneet_singh_1cd12e795b68</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%2F4218108%2F0e468a17-9f77-4c64-9764-432ae7279966.png</url>
      <title>DEV Community: Baneet Singh</title>
      <link>https://dev.to/baneet_singh_1cd12e795b68</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/baneet_singh_1cd12e795b68"/>
    <language>en</language>
    <item>
      <title>Your Shopify price test is picking the wrong winner</title>
      <dc:creator>Baneet Singh</dc:creator>
      <pubDate>Sat, 31 Oct 2026 02:06:30 +0000</pubDate>
      <link>https://dev.to/baneet_singh_1cd12e795b68/your-shopify-price-test-is-picking-the-wrong-winner-4bl6</link>
      <guid>https://dev.to/baneet_singh_1cd12e795b68/your-shopify-price-test-is-picking-the-wrong-winner-4bl6</guid>
      <description>&lt;p&gt;Most price tests on Shopify pick the wrong winner, and the reason is the metric, not the tooling.&lt;/p&gt;

&lt;p&gt;A lower price almost always converts better. If you judge a price test on conversion rate, the cheapest price wins every time, even when it makes you less money.&lt;/p&gt;

&lt;h2&gt;
  
  
  Judge price tests on profit per visitor
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;profit per visitor = conversion rate × (price − COGS − shipping − fees)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A worked example with $15 of cost per order:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Conversion rate&lt;/th&gt;
&lt;th&gt;Profit per order&lt;/th&gt;
&lt;th&gt;Profit per visitor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;$40&lt;/td&gt;
&lt;td&gt;3.0%&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.75&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$48&lt;/td&gt;
&lt;td&gt;2.7%&lt;/td&gt;
&lt;td&gt;$33&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.89&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The $48 price converts 10% worse and earns 19% more per visitor. A conversion-rate dashboard would have told you to keep $40.&lt;/p&gt;

&lt;p&gt;Here is the same math as a tiny function you can drop into a notebook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profitPerVisitor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;profitPerVisitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.03&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// 0.75&lt;/span&gt;
&lt;span class="nf"&gt;profitPerVisitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.027&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 0.891&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Three ways to run the test on Shopify
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate variants.&lt;/strong&gt; Each price is a hidden copy of the variant and an app shows one copy per visitor. Easy, but inventory and reports split across the copies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shopify Functions (Plus only).&lt;/strong&gt; A cart transform sets the price at checkout. Clean data, needs Plus and a developer or an app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate offer pages.&lt;/strong&gt; Two landing pages with different prices or bundles, traffic split by link. Only works for paid traffic you can route.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Rules that keep it honest
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;One visitor sees one price for the whole test, on every page and at checkout.&lt;/li&gt;
&lt;li&gt;Charge the price that was shown. Never raise it at checkout.&lt;/li&gt;
&lt;li&gt;Run whole weeks. Price tests need more traffic than page tests because the profit gap is small.&lt;/li&gt;
&lt;li&gt;Check local consumer law before showing different prices to different people.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sample size
&lt;/h2&gt;

&lt;p&gt;Profit differences are small, so you need more orders than you think. At a 2% conversion rate, detecting a 20% difference takes roughly 21,000 visitors per version at 95% confidence and 80% power. There is a &lt;a href="https://www.luriart.com/learn/ab-test-sample-size" rel="noopener noreferrer"&gt;free calculator and table here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;I build &lt;a href="https://www.luriart.com" rel="noopener noreferrer"&gt;Luria&lt;/a&gt;, which runs split tests on the parts of a page around the price (photos, offer wording, buy button, reviews placement), judged on real orders. For changing the price itself, a dedicated price testing app is the right tool. Full guide: &lt;a href="https://www.luriart.com/learn/shopify-price-testing" rel="noopener noreferrer"&gt;How to A/B test prices on Shopify&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>startup</category>
    </item>
    <item>
      <title>Running website A/B tests from Claude Code with an MCP server</title>
      <dc:creator>Baneet Singh</dc:creator>
      <pubDate>Sat, 31 Oct 2026 01:30:18 +0000</pubDate>
      <link>https://dev.to/baneet_singh_1cd12e795b68/running-website-ab-tests-from-claude-code-with-an-mcp-server-3b8f</link>
      <guid>https://dev.to/baneet_singh_1cd12e795b68/running-website-ab-tests-from-claude-code-with-an-mcp-server-3b8f</guid>
      <description>&lt;p&gt;Coding agents can now ship a landing page in minutes. The slow part is finding out whether the new page actually converts better than the old one. That needs a split test on real traffic, which usually means a testing tool, a dashboard, and someone babysitting it.&lt;/p&gt;

&lt;p&gt;Here is how to let the agent run that loop itself over MCP: add the site, install the tag, start a test, and read the winner, all from the terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  What MCP gives the agent
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; lets a client (Claude Code, Claude, ChatGPT, Cursor, Codex) call tools on a remote server. For split testing, the useful tools are roughly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;setup:&lt;/strong&gt; add a site, check the tag is installed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;tests:&lt;/strong&gt; create a test, list tests, read one test's numbers, change or stop it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;evidence:&lt;/strong&gt; heatmaps, session recordings, what past tests taught, what to test next&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;conversions:&lt;/strong&gt; record a sale or sign-up from your backend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will use &lt;a href="https://www.luriart.com/" rel="noopener noreferrer"&gt;Luria's&lt;/a&gt; MCP server because I build it, but the workflow is the same idea with any testing tool that exposes an API (PostHog has an MCP server for feature-flag experiments, for example).&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Connect
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http luria https://www.luriart.com/api/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first call opens a browser: sign in with an emailed link and press Allow. No API key to paste. (Clients without OAuth can send &lt;code&gt;Authorization: Bearer lk_...&lt;/code&gt; instead.) Cursor, Codex, VS Code and others take the same URL as a remote Streamable HTTP server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"luria"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.luriart.com/api/mcp"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Add the site and the tag
&lt;/h2&gt;

&lt;p&gt;Prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Add example.com to Luria and install the tag in this repo's layout.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent calls &lt;code&gt;add_site&lt;/code&gt;, gets back a one-line script tag and per-framework install notes, edits your layout file, then calls &lt;code&gt;verify_install&lt;/code&gt; until the tag reports in. Until a site is connected, every other tool answers &lt;code&gt;setup_needed&lt;/code&gt; with the exact next steps, so the agent cannot wander off and create tests on nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Start a test
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Start a test on the homepage headline. Keep the current one as the control.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent calls &lt;code&gt;create_test&lt;/code&gt; (or &lt;code&gt;auto_test&lt;/code&gt; to let Luria write the version from the page and the site's data). Two things worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The first live change waits for the owner's OK.&lt;/strong&gt; An agent can propose; a human approves the first one. After that, tests can run on their own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The original stays live as a held-out control&lt;/strong&gt; and never drops below a floor of traffic, so the result is measured against the same visitors at the same time, not a before/after guess.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Count real conversions
&lt;/h2&gt;

&lt;p&gt;Clicks are not wins. If checkout happens on your backend (Stripe, a booking tool), send the conversion server-side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://www.luriart.com/api/v1/conversions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$LURIA_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"site":"SITE_ID","visitor":"&amp;lt;luria_sid from the browser&amp;gt;","name":"purchase","value":49.99,"order_id":"1042"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or just ask the agent: &lt;em&gt;"Wire purchase conversions from our Stripe webhook to Luria."&lt;/em&gt; It has the &lt;code&gt;record_conversion&lt;/code&gt; tool and your codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Read the result
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;How is the headline test doing? If there is a winner, keep it and start the next test you suggest.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent calls &lt;code&gt;get_test&lt;/code&gt; (visits, conversions and the chance each version beats the original), and &lt;code&gt;next_tests&lt;/code&gt; / &lt;code&gt;start_next_test&lt;/code&gt; to keep going. A change is kept only when it is 95% likely to beat the original after at least 200 visits per version. If you want to sanity-check how long that should take, here is a &lt;a href="https://www.luriart.com/learn/ab-test-sample-size" rel="noopener noreferrer"&gt;sample size calculator&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guardrails I would insist on (with any tool)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;A human approves the first live change.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A kill switch&lt;/strong&gt; that stops every change fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spend caps&lt;/strong&gt;: prepaid credits or a hard monthly limit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read-only tools marked as read-only&lt;/strong&gt; (&lt;code&gt;readOnlyHint&lt;/code&gt;), so the client can auto-approve reads and ask for writes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Errors that say what to send next&lt;/strong&gt; (&lt;code&gt;{"error", "code", "hint"}&lt;/code&gt;), so the agent fixes its call instead of guessing.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Setup guide: &lt;a href="https://www.luriart.com/learn/ab-testing-with-claude-and-chatgpt" rel="noopener noreferrer"&gt;How to run A/B tests from Claude, ChatGPT or Cursor&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Full tool list for agents: &lt;a href="https://www.luriart.com/llms.txt" rel="noopener noreferrer"&gt;luriart.com/llms.txt&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Server docs and registry entry: &lt;a href="https://github.com/baneets/luria-mcp" rel="noopener noreferrer"&gt;github.com/baneets/luria-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Other options compared: &lt;a href="https://www.luriart.com/compare/ab-testing-for-ai-agents" rel="noopener noreferrer"&gt;A/B testing tools an AI agent can use&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I'm the founder of Luria. New accounts get 1,000 free credits if you want to try the loop above.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>claude</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Thompson sampling for A/B tests in 40 lines of JavaScript (and the trap nobody mentions)</title>
      <dc:creator>Baneet Singh</dc:creator>
      <pubDate>Sat, 31 Oct 2026 01:25:45 +0000</pubDate>
      <link>https://dev.to/baneet_singh_1cd12e795b68/thompson-sampling-for-ab-tests-in-40-lines-of-javascript-and-the-trap-nobody-mentions-2h4h</link>
      <guid>https://dev.to/baneet_singh_1cd12e795b68/thompson-sampling-for-ab-tests-in-40-lines-of-javascript-and-the-trap-nobody-mentions-2h4h</guid>
      <description>&lt;p&gt;A classic A/B test splits traffic 50/50 until it reaches a fixed sample size. A &lt;strong&gt;multi-armed bandit&lt;/strong&gt; moves traffic toward the version that is winning &lt;em&gt;while the test runs&lt;/em&gt;, so fewer visitors see the loser. Thompson sampling is the simplest good way to do it.&lt;/p&gt;

&lt;p&gt;Here is a working implementation with zero dependencies, a simulation, and the one trap that makes a lot of bandit "wins" impossible to prove.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea in one paragraph
&lt;/h2&gt;

&lt;p&gt;For each version, keep a belief about its conversion rate: a Beta distribution built from its visits and conversions, &lt;code&gt;Beta(1 + conversions, 1 + visits - conversions)&lt;/code&gt;. For every new visitor, draw one plausible rate from each version's belief and show the version with the highest draw. Versions you are unsure about still get explored; versions that are clearly worse get picked less and less.&lt;/p&gt;

&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;JavaScript has no built-in Beta sampler, so we build one from two Gamma draws (Marsaglia-Tsang):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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;random&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;v&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;random&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="o"&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="nx"&gt;u&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;cos&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="nx"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Gamma(k, 1), Marsaglia-Tsang; boost trick for k &amp;lt; 1.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;gamma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&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;gamma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&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="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;random&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;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(;;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;u&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;random&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.0331&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;4&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="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;*&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="nx"&gt;v&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="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;beta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;gamma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;gamma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Pick a version for the next visitor.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;pick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&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="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bestDraw&lt;/span&gt; &lt;span class="o"&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="nx"&gt;arms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;arm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;draw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;beta&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="nx"&gt;arm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;conversions&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="nx"&gt;arm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visits&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;arm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;conversions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;draw&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;bestDraw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;bestDraw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;best&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Chance that B beats A, by simulation.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;chanceBeats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;draws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;wins&lt;/span&gt; &lt;span class="o"&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;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;draws&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;beta&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="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;conversions&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="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visits&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;conversions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;beta&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="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;conversions&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="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visits&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;conversions&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;wins&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;wins&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;draws&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;h2&gt;
  
  
  Simulate it
&lt;/h2&gt;

&lt;p&gt;Two versions, true conversion rates 2.0% and 2.5%, 40,000 visitors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;truth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.025&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;truth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;visits&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="na"&gt;conversions&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;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;40000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arms&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;arms&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;visits&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&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;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;truth&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="nx"&gt;arms&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;conversions&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;console&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="nx"&gt;arms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;chanceBeats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arms&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="nx"&gt;arms&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two runs on my machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A: 7,745 visits, 160 conversions   B: 32,255 visits, 801 conversions   P(B&amp;gt;A) = 0.984
A: 2,278 visits,  41 conversions   B: 37,722 visits, 975 conversions   P(B&amp;gt;A) = 0.992
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bandit found the better version and sent it most of the traffic. A 50/50 test would have shown the worse page to 20,000 people.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap: you starve your control
&lt;/h2&gt;

&lt;p&gt;Look at the second run: the original page got only &lt;strong&gt;2,278 visits&lt;/strong&gt;. That is fine when you only care about earning more during the test, but it is a problem when someone asks &lt;em&gt;"did the change actually help, and by how much?"&lt;/em&gt; With so few visits on the original, the estimate of the original's rate is wide, and seasonality or an ad change can fool you.&lt;/p&gt;

&lt;p&gt;The fix is boring and important: &lt;strong&gt;keep a floor on the original.&lt;/strong&gt; Never let it drop below some share of traffic (10% to 20% is common), and only call a winner after a minimum number of visits per version (a few hundred at least) and a high bar (for example 95% chance to beat the original).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;pickWithFloor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;floor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&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;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// arm 0 = your unchanged page&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;pick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arms&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;h2&gt;
  
  
  When to use which
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fixed 50/50 test:&lt;/strong&gt; you need a precise size of effect for a big decision (pricing, a redesign) and will reuse the learning for months.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bandit with a floor on the original:&lt;/strong&gt; everyday page copy, where earning more during the test matters and you still want proof against the unchanged page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want the sample-size side of this, I wrote that up too: &lt;a href="https://www.luriart.com/learn/ab-test-sample-size" rel="noopener noreferrer"&gt;How many visitors does an A/B test actually need?&lt;/a&gt; and a longer comparison of &lt;a href="https://www.luriart.com/learn/multi-armed-bandit-vs-ab-testing" rel="noopener noreferrer"&gt;bandits vs A/B tests&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I build &lt;a href="https://www.luriart.com/" rel="noopener noreferrer"&gt;Luria&lt;/a&gt;, which runs Thompson sampling with a floor on the original page for store split tests. The code above is standalone and MIT-style; use it anywhere.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>statistics</category>
      <category>abtesting</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>How many visitors does an A/B test actually need? (table + free calculator)</title>
      <dc:creator>Baneet Singh</dc:creator>
      <pubDate>Sat, 31 Oct 2026 01:24:47 +0000</pubDate>
      <link>https://dev.to/baneet_singh_1cd12e795b68/how-many-visitors-does-an-ab-test-actually-need-table-free-calculator-52bc</link>
      <guid>https://dev.to/baneet_singh_1cd12e795b68/how-many-visitors-does-an-ab-test-actually-need-table-free-calculator-52bc</guid>
      <description>&lt;p&gt;Most A/B tests that "won" never had enough visitors to win anything. Here is the math, a table you can bookmark, and a tiny zero-dependency calculator.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;p&gt;At a &lt;strong&gt;2% conversion rate&lt;/strong&gt;, detecting a &lt;strong&gt;20% relative lift&lt;/strong&gt; (2.0% to 2.4%) takes about &lt;strong&gt;21,106 visitors per version&lt;/strong&gt; at 95% confidence and 80% power. Two versions means about 42,000 visitors on that page.&lt;/p&gt;

&lt;p&gt;Smaller lifts and lower conversion rates need a lot more:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Current conversion rate&lt;/th&gt;
&lt;th&gt;Detect +10%&lt;/th&gt;
&lt;th&gt;Detect +20%&lt;/th&gt;
&lt;th&gt;Detect +30%&lt;/th&gt;
&lt;th&gt;Detect +50%&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;163,092&lt;/td&gt;
&lt;td&gt;42,691&lt;/td&gt;
&lt;td&gt;19,824&lt;/td&gt;
&lt;td&gt;7,747&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2%&lt;/td&gt;
&lt;td&gt;80,679&lt;/td&gt;
&lt;td&gt;21,106&lt;/td&gt;
&lt;td&gt;9,795&lt;/td&gt;
&lt;td&gt;3,823&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3%&lt;/td&gt;
&lt;td&gt;53,208&lt;/td&gt;
&lt;td&gt;13,911&lt;/td&gt;
&lt;td&gt;6,452&lt;/td&gt;
&lt;td&gt;2,515&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5%&lt;/td&gt;
&lt;td&gt;31,231&lt;/td&gt;
&lt;td&gt;8,155&lt;/td&gt;
&lt;td&gt;3,778&lt;/td&gt;
&lt;td&gt;1,468&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;14,749&lt;/td&gt;
&lt;td&gt;3,839&lt;/td&gt;
&lt;td&gt;1,772&lt;/td&gt;
&lt;td&gt;707&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Visitors per version. Two-sided test, 95% confidence, 80% power.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The formula
&lt;/h2&gt;

&lt;p&gt;For two proportions p1 (current) and p2 (with the lift):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight r"&gt;&lt;code&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z_a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;z_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with z_a/2 = 1.96 for 95% confidence (two-sided) and z_b = 0.84 for 80% power.&lt;/p&gt;

&lt;p&gt;In JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.959964&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.841621&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sampleSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lift&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;*&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="nx"&gt;lift&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;ceil&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;Z&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;Z&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;*&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="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;p2&lt;/span&gt; &lt;span class="o"&gt;*&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="nx"&gt;p2&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="nx"&gt;p2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;sampleSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 21106&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I put this (plus an inverse-normal function so you can change confidence and power, and a CLI) in a small MIT repo: &lt;a href="https://github.com/baneets/ab-test-sample-size" rel="noopener noreferrer"&gt;baneets/ab-test-sample-size&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx github:baneets/ab-test-sample-size 2 20 50000
&lt;span class="c"&gt;# 21,106 visitors per version (42,212 total), 95% confidence, 80% power&lt;/span&gt;
&lt;span class="c"&gt;# about 3.7 weeks at 50,000 visitors a month&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How long that takes
&lt;/h2&gt;

&lt;p&gt;A page with 50,000 visitors a month at 2% needs about &lt;strong&gt;3.7 weeks&lt;/strong&gt; to detect a 20% lift, and about &lt;strong&gt;14 weeks&lt;/strong&gt; for a 10% lift. At 10,000 visitors a month, the 20% case takes about &lt;strong&gt;18 weeks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is why button-colour tests on low-traffic stores almost never produce a real answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three mistakes that create fake winners
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Peeking.&lt;/strong&gt; Checking a fixed-sample test every day and stopping the first time it looks significant inflates false positives a lot. Either commit to the sample size, or use a method built to be checked as it runs (sequential tests, Bayesian tests, multi-armed bandits).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiny effects on small traffic.&lt;/strong&gt; If your page gets 5,000 visits a month, test big changes (offer, headline, first screen), not small ones. Big lifts need far fewer visitors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not running whole weeks.&lt;/strong&gt; People buy differently on Monday and Saturday. Run at least one full week even if the number is reached sooner.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Bandits vs fixed tests
&lt;/h2&gt;

&lt;p&gt;A classic A/B test splits traffic 50/50 until the sample is reached. A multi-armed bandit (often Thompson sampling) moves traffic toward the version that is winning while the test runs, so fewer visitors see the loser. The catch: a pure bandit can starve your original page, which makes the gain hard to prove. The fix is to keep a holdout share on the original no matter what.&lt;/p&gt;

&lt;p&gt;I wrote more on that here: &lt;a href="https://www.luriart.com/learn/multi-armed-bandit-vs-ab-testing" rel="noopener noreferrer"&gt;multi-armed bandit vs A/B testing&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it in the browser
&lt;/h2&gt;

&lt;p&gt;If you just want the number without installing anything, there is an interactive version here: &lt;a href="https://www.luriart.com/learn/ab-test-sample-size" rel="noopener noreferrer"&gt;A/B test sample size calculator&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I build &lt;a href="https://www.luriart.com/" rel="noopener noreferrer"&gt;Luria&lt;/a&gt;, an AI split-testing tool, which is why I care about this. The math above is standard and works with any testing tool.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>abtesting</category>
      <category>statistics</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
