diff --git a/go.mod b/go.mod
index 73c7f9d3550dda843c691dbcf034230aebbf4818..5d7a533b1440c7aa1c60b053f4a6762592f144b2 100644
--- a/go.mod
+++ b/go.mod
@@ -7,19 +7,19 @@ replace git.autistici.org/ale/lb => 0xacab.org/leap/lb v0.0.0-20210225193050-570
 require (
 	0xacab.org/leap/bitmask-core v0.0.0-20241127074806-6cb14d2eb811
 	git.autistici.org/ale/lb v0.0.0-20210301105648-288f2f5853b7
-	github.com/emirpasic/gods v1.18.1
 	github.com/emirpasic/gods/v2 v2.0.0-alpha
 	github.com/go-openapi/errors v0.20.4
 	github.com/go-openapi/runtime v0.26.0
 	github.com/go-openapi/strfmt v0.21.7
 	github.com/go-openapi/swag v0.22.4
 	github.com/go-openapi/validate v0.22.1
+	github.com/jmcvetta/randutil v0.0.0-20150817122601-2bb1b664bcff
 	github.com/jmoiron/sqlx v1.3.5
 	github.com/kellydunn/golang-geo v0.7.0
 	github.com/labstack/echo/v4 v4.11.1
 	github.com/magiconair/properties v1.8.7
 	github.com/mattn/go-sqlite3 v1.14.22
-	github.com/mroth/weightedrand v1.0.0
+	github.com/mroth/weightedrand/v2 v2.1.0
 	github.com/prometheus/client_golang v1.11.1
 	github.com/rs/zerolog v1.33.0
 	github.com/spf13/cobra v1.8.1
diff --git a/go.sum b/go.sum
index 4daf85f81e653954952848cf93308ddb4bc8df1b..2ea8e03d4d158b4caa06749f1a82dc48590337a2 100644
--- a/go.sum
+++ b/go.sum
@@ -83,8 +83,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=
 github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc=
-github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
-github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
 github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
 github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -253,6 +251,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/jmcvetta/randutil v0.0.0-20150817122601-2bb1b664bcff h1:6NvhExg4omUC9NfA+l4Oq3ibNNeJUdiAF3iBVB0PlDk=
+github.com/jmcvetta/randutil v0.0.0-20150817122601-2bb1b664bcff/go.mod h1:ddfPX8Z28YMjiqoaJhNBzWHapTHXejnB5cDCUWDwriw=
 github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
 github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
@@ -328,8 +328,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
-github.com/mroth/weightedrand v1.0.0 h1:V8JeHChvl2MP1sAoXq4brElOcza+jxLkRuwvtQu8L3E=
-github.com/mroth/weightedrand v1.0.0/go.mod h1:3p2SIcC8al1YMzGhAIoXD+r9olo/g/cdJgAD905gyNE=
+github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
+github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
diff --git a/pkg/api/endpoints.go b/pkg/api/endpoints.go
index 619d0ac4358aa9e19115bb2ba6889bd8cc491a04..c568c589fe4a77a2838b91413b5ddd7296a79fbf 100644
--- a/pkg/api/endpoints.go
+++ b/pkg/api/endpoints.go
@@ -11,8 +11,9 @@ import (
 	"0xacab.org/leap/menshen/pkg/geolocate"
 	"0xacab.org/leap/menshen/pkg/latency"
 	m "0xacab.org/leap/menshen/pkg/models"
+	wr "0xacab.org/leap/menshen/pkg/weightedrand"
 	rbt "github.com/emirpasic/gods/v2/trees/redblacktree"
-	wr "github.com/mroth/weightedrand"
+	"github.com/labstack/echo/v4"
 )
 
 func filter[T any](f func(t T) bool, items []T) []T {
@@ -67,28 +68,29 @@ func sortEndpoints[T m.Bridge | m.Gateway](cc string, endpoints []*T, locations
 		}
 		for range list {
 			// append random element from current list to result
-			element := chooser.Pick().(*T)
-			if element != nil {
-				result = append(result, chooser.Pick().(*T))
+			element, err := chooser.PickRandom()
+			if err != nil {
+				return nil, fmt.Errorf("failed to sort endpoint list: %v", err)
 			}
+			result = append(result, *element)
 		}
 	}
 
 	return result, nil
 }
 
-func createLocationTree[T m.Bridge | m.Gateway](endpoints []*T, locationWeights map[string]float64) *rbt.Tree[float64, []wr.Choice] {
-	tree := rbt.New[float64, []wr.Choice]()
+func createLocationTree[T m.Bridge | m.Gateway](endpoints []*T, locationWeights map[string]float64) *rbt.Tree[float64, []wr.Choice[*T]] {
+	tree := rbt.New[float64, []wr.Choice[*T]]()
 	for _, endpoint := range endpoints {
-		var list []wr.Choice
+		var list []wr.Choice[*T]
 		found := false
 		locationWeight := locationWeights[getLocation(endpoint)]
 		if list, found = tree.Get(locationWeight); !found {
 			// TODO: we want to set weights per gateways, so that gateways with higher capacities
 			// are priotized, the wr.Choice wrapper is a preparation for that
-			list = []wr.Choice{{Item: endpoint, Weight: 1}}
+			list = []wr.Choice[*T]{{Item: endpoint, Weight: 1}}
 		} else {
-			list = append(list, wr.Choice{Item: endpoint, Weight: 1})
+			list = append(list, wr.Choice[*T]{Item: endpoint, Weight: 1})
 		}
 		tree.Put(locationWeight, list)
 	}
@@ -96,18 +98,26 @@ func createLocationTree[T m.Bridge | m.Gateway](endpoints []*T, locationWeights
 }
 
 func createWeightedRandomList[T m.Bridge | m.Gateway](endpoints []*T) ([]*T, error) {
-	var list []wr.Choice = make([]wr.Choice, len(endpoints))
+	list := []wr.Choice[T]{}
 	for _, endpoint := range endpoints {
-		list = append(list, wr.Choice{Item: endpoint, Weight: 1})
+		if endpoint == nil {
+			continue
+		}
+		list = append(list, wr.Choice[T]{Item: *endpoint, Weight: 1})
 	}
 	choser, err := wr.NewChooser(list...)
 	if err != nil {
 		return nil, err
 	}
-	for i := 0; i < len(endpoints); i++ {
-		endpoints[i] = choser.Pick().(*T)
+	result := []*T{}
+	for range endpoints {
+		choice, err := choser.PickRandom()
+		if err != nil {
+			return nil, fmt.Errorf("failed to create weighted random endpoint list: %v", err)
+		}
+		result = append(result, choice)
 	}
-	return endpoints, nil
+	return result, nil
 }
 
 func getLocation(endpoint interface{}) string {
diff --git a/pkg/api/endpoints_test.go b/pkg/api/endpoints_test.go
index c2850fb2cdc78ba98fe8bc65c4750afdbbca7fee..5397235f8805fc10894c629b9bc3740cde48338f 100644
--- a/pkg/api/endpoints_test.go
+++ b/pkg/api/endpoints_test.go
@@ -1,13 +1,18 @@
 package api
 
 import (
+	"encoding/json"
 	"errors"
 	"fmt"
+	"net/http"
+	"net/http/httptest"
 	"testing"
 
 	"0xacab.org/leap/menshen/pkg/latency"
 	m "0xacab.org/leap/menshen/pkg/models"
+	"github.com/labstack/echo/v4"
 	"github.com/magiconair/properties/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func TestSortEndpoints(t *testing.T) {
@@ -80,12 +85,14 @@ func TestSortEndpoints(t *testing.T) {
 	}
 
 	for ti, test := range testTable {
-		sortedList, err := sortEndpoints(test.cc, gateways, locationmap, lm)
-		assert.Equal(t, err, test.expextedErr)
-		assert.Equal(t, len(sortedList), len(test.expected))
-		for i, entry := range sortedList {
-			assert.Equal(t, entry.Location, test.expected[i], fmt.Sprintf("test %d: item %d", ti+1, i+1))
-		}
+		t.Run(test.name, func(t *testing.T) {
+			sortedList, err := sortEndpoints(test.cc, gateways, locationmap, lm)
+			assert.Equal(t, err, test.expextedErr)
+			assert.Equal(t, len(sortedList), len(test.expected))
+			for i, entry := range sortedList {
+				assert.Equal(t, entry.Location, test.expected[i], fmt.Sprintf("test %d: item %d", ti+1, i+1))
+			}
+		})
 	}
 }
 
@@ -176,16 +183,16 @@ func TestSortBridges_RandomOrderWithinLocation(t *testing.T) {
 		Lon:         "-73.56",
 	}
 
-	bridge1 := &m.Gateway{Location: locations[0], Host: "gw1_ny"}
-	bridge2 := &m.Gateway{Location: locations[0], Host: "gw2_ny"}
-	bridge3 := &m.Gateway{Location: locations[1], Host: "gw3_par"}
-	bridge4 := &m.Gateway{Location: locations[1], Host: "gw4_par"}
-	bridge5 := &m.Gateway{Location: locations[2], Host: "gw5_mo"}
-	bridge6 := &m.Gateway{Location: locations[2], Host: "gw6_mo"}
+	bridge1 := &m.Bridge{Location: locations[0], Host: "gw1_ny"}
+	bridge2 := &m.Bridge{Location: locations[0], Host: "gw2_ny"}
+	bridge3 := &m.Bridge{Location: locations[1], Host: "gw3_par"}
+	bridge4 := &m.Bridge{Location: locations[1], Host: "gw4_par"}
+	bridge5 := &m.Bridge{Location: locations[2], Host: "gw5_mo"}
+	bridge6 := &m.Bridge{Location: locations[2], Host: "gw6_mo"}
 
 	lm, _ := latency.NewMetric()
 	locationmap := locationMap{locations[0]: locationStruct1, locations[1]: locationStruct2, locations[2]: locationStruct3}
-	bridges := []*m.Gateway{bridge1, bridge2, bridge3, bridge4, bridge5, bridge6}
+	bridges := []*m.Bridge{bridge1, bridge2, bridge3, bridge4, bridge5, bridge6}
 
 	firstResult, _ := sortEndpoints("US", bridges, locationmap, lm)
 	differentHosts := map[string]bool{"New York": false, "Paris": false, "Montreal": false}
@@ -237,9 +244,99 @@ func TestSanitizePort(t *testing.T) {
 }
 
 func TestSanitizeTransport(t *testing.T) {
-	assert.Equal(t, sanitizeTransport("udp", []string{"tcp", "udp"}), nil)
-	assert.Equal(t, sanitizeTransport("udp", []string{"tcp"}), errors.New("unknown transport"))
-	assert.Equal(t, sanitizeTransport("", []string{"tcp", "udp"}), nil)
+	testTable := []struct {
+		name              string
+		url               string
+		allowedTransports []string
+		expectedParam     string
+		expextedErr       error
+	}{
+		{
+			"transport allowed",
+			"/dummyendpoint?tr=udp",
+			[]string{"tcp", "udp"},
+			"udp",
+			nil,
+		},
+		{
+			"transport not allowed return error",
+			"/dummyendpoint?tr=udp",
+			[]string{"tcp"},
+			"udp",
+			errors.New("unknown value udp for query param tr"),
+		},
+		{
+			"no transport parameter",
+			"/dummyendpoint",
+			[]string{"tcp", "udp"},
+			"",
+			nil,
+		},
+		{
+			"uppercase transport parameter to lowercase",
+			"/dummyendpoint?tr=UDP",
+			[]string{"tcp", "udp"},
+			"udp",
+			nil,
+		},
+	}
+	for _, tc := range testTable {
+		t.Run(tc.name, func(t *testing.T) {
+			e := echo.New()
+			req := httptest.NewRequest(http.MethodGet, tc.url, nil)
+			c := e.NewContext(req, nil)
+			assert.Equal(t, sanitizeTransport(c, tc.allowedTransports), tc.expextedErr)
+			assert.Equal(t, c.QueryParam("tr"), tc.expectedParam)
+		})
+	}
+}
+
+func TestSanitizeType(t *testing.T) {
+	testTable := []struct {
+		name              string
+		url               string
+		allowedTransports []string
+		expectedParam     string
+		expextedErr       error
+	}{
+		{
+			"transport allowed",
+			"/dummyendpoint?type=obfs4",
+			[]string{"obfs4-hop", "obfs4"},
+			"obfs4",
+			nil,
+		},
+		{
+			"transport not allowed return error",
+			"/dummyendpoint?type=kcp",
+			[]string{"obfs4-hop", "obfs4"},
+			"kcp",
+			errors.New("unknown value kcp for query param type"),
+		},
+		{
+			"no transport parameter",
+			"/dummyendpoint",
+			[]string{"obfs4-hop", "obfs4"},
+			"",
+			nil,
+		},
+		{
+			"uppercase transport parameter to lowercase",
+			"/dummyendpoint?type=OBFS4-HOP",
+			[]string{"obfs4-hop", "obfs4"},
+			"obfs4-hop",
+			nil,
+		},
+	}
+	for _, tc := range testTable {
+		t.Run(tc.name, func(t *testing.T) {
+			e := echo.New()
+			req := httptest.NewRequest(http.MethodGet, tc.url, nil)
+			c := e.NewContext(req, nil)
+			assert.Equal(t, sanitizeType(c, tc.allowedTransports), tc.expextedErr)
+			assert.Equal(t, c.QueryParam("type"), tc.expectedParam)
+		})
+	}
 }
 
 func TestSanitizeLocation(t *testing.T) {
@@ -274,3 +371,52 @@ func TestSanitizeLocation(t *testing.T) {
 	assert.Equal(t, sanitizeLocations("Paris", locationmap), nil)
 	assert.Equal(t, sanitizeLocations("", locationmap), nil)
 }
+
+func TestCreateWeightedRandomList(t *testing.T) {
+	bridge1 := &m.Bridge{Location: "ny", Host: "gw1_ny"}
+	bridge2 := &m.Bridge{Location: "ny", Host: "gw2_ny"}
+	list := []*m.Bridge{bridge1, bridge2}
+
+	bridge1First := false
+	bridge2First := false
+	for range 100 {
+		randomlist, err := createWeightedRandomList(list)
+		assert.Equal(t, nil, err)
+		bridge1Found := false
+		bridge2Found := false
+		for i, element := range randomlist {
+			// "deep" comparison of pointers content via marshalling work-around
+			jsonElement, err := toJson(*element)
+			require.NoError(t, err)
+			jsonBridge1, err := toJson(*bridge1)
+			require.NoError(t, err)
+			jsonBridge2, err := toJson(*bridge2)
+			require.NoError(t, err)
+
+			if jsonElement == jsonBridge1 {
+				bridge1Found = true
+				if i == 0 {
+					bridge1First = true
+				}
+			} else if jsonElement == jsonBridge2 {
+				bridge2Found = true
+				if i == 0 {
+					bridge2First = true
+				}
+			}
+		}
+		assert.Equal(t, bridge1Found, true, "bridge 1 found in randomized list")
+		assert.Equal(t, bridge2Found, true, "bridge 2 found in randomized list")
+	}
+	assert.Equal(t, bridge1First, true, "bridge 1 at least once at first position in randomized lsit")
+	assert.Equal(t, bridge2First, true, "bridge 2 at least once at first position in randomized lsit")
+
+}
+
+func toJson(model any) (string, error) {
+	res, err := json.Marshal(model)
+	if err != nil {
+		return "", err
+	}
+	return string(res), nil
+}
diff --git a/pkg/weightedrand/weightedrand.go b/pkg/weightedrand/weightedrand.go
new file mode 100644
index 0000000000000000000000000000000000000000..a452ca25732484c661487cf7045e0f7a1dddb6c5
--- /dev/null
+++ b/pkg/weightedrand/weightedrand.go
@@ -0,0 +1,82 @@
+// Package weightedrand contains a data structure and algorithm used
+// to randomly select an element from some kind of list, where the chances of
+// each element to be selected not being equal, but defined by relative
+// "weights" (or probabilities). This is called weighted random selection.
+//
+// API inspied by mroth's https://github.com/mroth/weightedrand/
+// adapted to avoid duplicate picks from the source list and opptimized for
+// smaller sets (up to 1e4)
+
+package weightedrand
+
+import (
+	"fmt"
+	"math/rand"
+	"sort"
+
+	"errors"
+)
+
+// Choice is a generic wrapper that can be used to add weights for any item.
+type Choice[T any] struct {
+	Item   T
+	Weight int
+}
+
+// NewChoice creates a new Choice with specified item and weight.
+func NewChoice[T any](item *T, weight int) Choice[*T] {
+	return Choice[*T]{Item: item, Weight: weight}
+}
+
+// A Chooser caches many possible Choices in a structure designed to improve
+// performance on repeated calls for weighted random selection.
+type Chooser[T any] struct {
+	items       []Choice[T]
+	totalWeight int
+}
+
+// NewChooser initializes a new Chooser for picking from the provided choices.
+func NewChooser[T any](choices ...Choice[T]) (*Chooser[T], error) {
+	sort.Slice(choices, func(i, j int) bool {
+		return choices[i].Weight < choices[j].Weight
+	})
+	chooser := Chooser[T]{items: choices}
+	chooser.totalWeight = 0
+	for _, item := range chooser.items {
+		if item.Weight < 1 {
+			return nil, errors.New("item weight cannot be < 1")
+		}
+		chooser.totalWeight += int(item.Weight)
+	}
+	return &chooser, nil
+}
+
+// PickRandom removes a random item from the list based on weights and returns it.
+// This method is not thread safe.
+func (c *Chooser[T]) PickRandom() (*T, error) {
+	if len(c.items) == 0 {
+		return nil, fmt.Errorf("no items left to pick")
+	}
+	if len(c.items) == 1 {
+		return &c.items[0].Item, nil
+	}
+
+	// Generate a random number between 0 and totalWeight
+	r := rand.Intn(c.totalWeight)
+
+	// Find the item corresponding to the random number
+	for i, item := range c.items {
+		if r < item.Weight {
+			// Remove the item from the list, that's ok
+			// within the loop since we're returning here
+			chosenItem := c.items[i]
+			c.items = append(c.items[:i], c.items[i+1:]...)
+			// keep total weight in sync with sum of item weights
+			c.totalWeight -= chosenItem.Weight
+			return &chosenItem.Item, nil
+		}
+		r -= item.Weight
+	}
+
+	return nil, fmt.Errorf("failed to pick an item")
+}
diff --git a/pkg/weightedrand/weightedrand_test.go b/pkg/weightedrand/weightedrand_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..33d4db79822f0068200b205cf5ae9645178b14a0
--- /dev/null
+++ b/pkg/weightedrand/weightedrand_test.go
@@ -0,0 +1,131 @@
+package weightedrand
+
+import (
+	"fmt"
+	"math"
+	"math/rand"
+	"testing"
+
+	"github.com/jmcvetta/randutil"
+	mrothwr "github.com/mroth/weightedrand/v2"
+)
+
+const BMMinChoices = 10
+const BMMaxChoices = 10_000_000
+
+func BenchmarkMultiple(b *testing.B) {
+	for n := BMMinChoices; n <= BMMaxChoices; n *= 10 {
+		b.Run(fmt.Sprintf("size=%s", fmt1eN(n)), func(b *testing.B) {
+			wr_choices := mockChoices(b, n)
+			ru_choices := convertChoices(b, wr_choices)
+			wr_menshen_choices := convertChoicesToMenshenWeightedRand(b, wr_choices)
+
+			b.Run("concurrency=single", func(b *testing.B) {
+				b.Run("lib=randutil", func(b *testing.B) {
+					for i := 0; i < b.N; i++ {
+						randutil.WeightedChoice(ru_choices)
+					}
+				})
+
+				b.Run("lib=mrothwr weightedrand", func(b *testing.B) {
+					chs, err := mrothwr.NewChooser(wr_choices...)
+					if err != nil {
+						b.Fatal(err)
+					}
+					b.ResetTimer()
+
+					for i := 0; i < b.N; i++ {
+						chs.Pick()
+					}
+				})
+
+				b.Run("lib=menshen weightedrand", func(b *testing.B) {
+					chs, err := NewChooser(wr_menshen_choices...)
+					if err != nil {
+						b.Fatal(err)
+					}
+					b.ResetTimer()
+
+					for i := 0; i < b.N; i++ {
+						chs.PickRandom()
+					}
+				})
+			})
+		})
+	}
+}
+
+// THE SINGLE USAGE CASE IS AN ANTI-PATTERN FOR THE INTENDED USAGE OF THIS
+// LIBRARY. Provide some optional benchmarks for that to illustrate the point.
+func BenchmarkSingle(b *testing.B) {
+	if testing.Short() {
+		b.Skip()
+	}
+
+	for n := BMMinChoices; n <= BMMaxChoices; n *= 10 {
+		b.Run(fmt.Sprintf("size=%s", fmt1eN(n)), func(b *testing.B) {
+			wr_choices := mockChoices(b, n)
+			ru_choices := convertChoices(b, wr_choices)
+			wr_menshen_choices := convertChoicesToMenshenWeightedRand(b, wr_choices)
+
+			b.Run("lib=randutil", func(b *testing.B) {
+				for i := 0; i < b.N; i++ {
+					randutil.WeightedChoice(ru_choices)
+				}
+			})
+
+			b.Run("lib=weightedrand", func(b *testing.B) {
+				for i := 0; i < b.N; i++ {
+					// never actually do this, this is not how the library is used
+					chs, _ := mrothwr.NewChooser(wr_choices...)
+					chs.Pick()
+				}
+			})
+
+			b.Run("lib=menshen weightedrand", func(b *testing.B) {
+				for i := 0; i < b.N; i++ {
+					// never actually do this, this is not how the library is used
+					chs, _ := NewChooser(wr_menshen_choices...)
+					chs.PickRandom()
+				}
+			})
+		})
+	}
+}
+
+func mockChoices(tb testing.TB, n int) []mrothwr.Choice[rune, uint] {
+	tb.Helper()
+	choices := make([]mrothwr.Choice[rune, uint], 0, n)
+	for i := 0; i < n; i++ {
+		s := '🥑'
+		w := rand.Intn(10) + 1
+		c := mrothwr.NewChoice(s, uint(w))
+		choices = append(choices, c)
+	}
+	return choices
+}
+
+func convertChoices(tb testing.TB, cs []mrothwr.Choice[rune, uint]) []randutil.Choice {
+	tb.Helper()
+	res := make([]randutil.Choice, len(cs))
+	for i, c := range cs {
+		res[i] = randutil.Choice{Weight: int(c.Weight), Item: c.Item}
+	}
+	return res
+}
+
+func convertChoicesToMenshenWeightedRand[T rune](tb testing.TB, cs []mrothwr.Choice[rune, uint]) []Choice[rune] {
+	tb.Helper()
+	res := make([]Choice[rune], len(cs))
+	for i, c := range cs {
+		res[i] = Choice[rune]{Weight: int(c.Weight), Item: c.Item}
+
+	}
+	return res
+}
+
+// fmt1eN returns simplified order of magnitude scientific notation for n,
+// e.g. "1e2" for 100, "1e7" for 10 million.
+func fmt1eN(n int) string {
+	return fmt.Sprintf("1e%d", int(math.Log10(float64(n))))
+}