Set up A/B testing using Cloudflare workers

Kumar Chetan Sharma
3 min readMar 22, 2024

If you’ve ever worked on a B2C web application, chances are you’ve encountered A/B testing. My first experience with A/B testing was back in 2011 at Yahoo!. Every new widget on the search page underwent A/B testing, with Yahoo! having its own setup for conducting these tests. However, in subsequent roles at different organizations, I noticed varying approaches to A/B testing, often limited to frontend modifications resulting in UI flickers.

Fast forward to this date. I use Cloudflare at work. Cloudflare offers a serverless feature known as “Workers,” which can be leveraged for A/B testing. The concept is straightforward:

  • Divide incoming traffic into buckets
  • Set a cookie, let us call this abcookie, for each bucket
  • Pass this cookie with every request to and from your application
  • Implement the necessary actions in your application based on these cookies
  • Achieve success!

Now, in Cloudflare workers:

  • Intercept every request
  • Check for the abcookie
  • If the cookie was found, pass the request to origin
  • If the cookie wasn’t found, create abcookie with its value to a test variant
  • Ensure the cookie is sent to origin for you to do your thing and to client for them to return it
  • That’s all

And here is an example code snippet

const weightedChoice = (options) => {
let totalWeight = 0;
options.forEach((option) => {
totalWeight += option.weight;
})
const randNum = Math.random() * totalWeight;
let weightSum = 0;
for (const option of options) {
weightSum += option.weight;
if (randNum < weightSum) {
return option.variant;
}
}
return options[options.length - 1].variant;
};

const options = [
{
experiment_name: 'Some fancy name for your experiment',
experiment_identifier: 'identifier_which_you_may_use_on_origin',
variants: [
// variant: bucket name
// weight: weight or % traffic for this variant,
// total of all weights should not exceed 1
{ variant: 'A', weight: 0.5 },
{ variant: 'B', weight: 0.5 },
],
},
{
experiment_name: 'this name is just for readability',
experiment_identifier: 'x456',
variants: [
// 3 variants
{ variant: 'A', weight: 0.3 },
{ variant: 'B', weight: 0.5 },
{ variant: 'C', weight: 0.2 },
],
},
{
experiment_name: 'it will not do anything',
experiment_identifier: 'x123',
variants: [
// 4 variants. Go wild!
{ variant: 'A', weight: 0.25 },
{ variant: 'B', weight: 0.25 },
{ variant: 'C', weight: 0.25 },
{ variant: 'D', weight: 0.25 },
],
},
];
const cookieJar = {};
options.forEach((option) => {
const chosenVariant = weightedChoice(option.variants);
cookieJar[option.experiment_identifier] = chosenVariant;
})

addEventListener('fetch', event => {
event.respondWith(handle(event.request));
})

async function handle(request) {
const abCookies = `ab=${JSON.stringify(cookieJar)}`;
// Check for existing cookie in request.
let cookie = request.headers.get('Cookie') || "";
// If the cookie is already set, just pass the request through.
if (cookie.includes(`ab`)) {
return fetch(request);
}
// Since cookie was not found, let us set it up.
// We need to clone the request so we can modify it.
let modifiedRequest = new Request(request);
modifiedRequest.headers.set('cookie', abCookies);
let response = await fetch(modifiedRequest);
// Similar to request, we need to clone the response
response = new Response(response.body, response);
// So that abcookie is returned to client
response.headers.set("Set-Cookie", abCookies);
return response;
}

This is just an example. Add🧂as per your taste. Or change the recipe to serve something new.

Implementing A/B testing with Cloudflare workers can significantly streamline the testing process and improve the overall user experience. If you found this information to be helpful feel free to share it with others. If not, then leave a comment.

--

--

Kumar Chetan Sharma

C0d3🐵 from 🇮🇳. selfTaught. 😨 of CS, Algos & DS. World famous 👨‍🍳 at home. Pro Level Pro-Crastinator. Man child. Dad of (✿◠‿◠)(◠‿◠✿)