21 Jan 2026
From time to time, a small and usually unimportant question gets stuck in my
head, and won’t stop nagging me until I spend the time to figure out the
answer. This post is about one of these.
Last December, I mentioned in a post that “bigger teams require more
coordination”, which would lead to diminishing returns to scale. As a team
grows, it requires managers to coordinate activities. Grow the team more, and
managers themselves require coordination, and another layer of managers, and so
on and so forth. My intuition was that, ignoring other effects, larger teams
would progressively become less and less productive, because of the increased
burden of management.
The question that got stuck in my head was, how does this burden grow? What
shape does it have, and can I express it as a mathematical function? This is
the question I will be investigating in this post.
The way I approached the question started by formulating a simplistic model,
without being too concerned about the finer details. In my world, any time we
form a group of a certain size, a coordinator (a manager, if you will) is
needed. These coordinators do not directly contribute to the actual work, but
they are necessary for the workers to perform their work.
As an illustration, let’s imagine that this group size is 5. In this case,
- 1 worker can work without coordination,
- 4 workers can work without coordination,
- 5 workers require 1 coordinator (we reached a group size of 5),
- 6 workers require 1 coordinator (we have 1 group of 5, and an unsupervised
worker),
- 25 workers require 5 coordinators, who themselves require 1 coordinator.
… and so on and so forth: 125 workers (5*5*5) require another layer of
coordinators, 625 yet another layer, etc…
So how does it look? Let’s write a function, computing the coordination
overhead needed for a given number of workers:
let groupSize = 5
let overhead (workers: int) =
workers
|> Seq.unfold (fun population ->
if population >= groupSize
then
let managers = population / groupSize
Some (managers, managers)
else None
)
More...
11 Dec 2025
This post is part of the F# Advent 2025 series, which already has
bangers! Check out the whole series, and a big shout out to Sergey Tihon
for organizing this once again!
It is that merry time of the year again! The holidays are approaching, and in
houses everywhere, people are happily sipping eggnogg and hanging decorations.
But in one house, the mood is not festive. Every year on December 1st, the
first day of Advent, Santa Claus begins wrapping gifts for 2 billion children
worldwide, from his workshop in the North Pole. But this year, Krampus
unexpectedly decided to impose tariffs on Greenland, throwing the supply chain
of gifts into chaos. It is now December 11th, and Santa just received the
goods. Santa is now 11 days behind schedule, and needs to hire many, many more
Elves than usual to catch up. But… how many Elves does he need to hire?
Santa runs a tight ship at Santa, Inc., and he knows that adding new Elves to
the team won’t be seamless. Bigger teams require more coordination and
additional equipment.
Based on available data, Santa knows that:
- He needs to hire Elves to wrap
2,000,000,000 gifts,
- A single Elf can wrap at most
100,000 gifts a day,
- Instead of the normal
24 Advent days, Santa has only 13 days left,
- A team of
n Elves will only be able to produce n ^ 0.8 as much as a
single elf, that is, there are diminishing returns to scale.
Note: the function f(elves) = elves ^ 0.8 is largely arbitrary. It has the
shape we want for our problem: it is always increasing (more elves can wrap
more gifts), but the increase slows down gradually. For instance f(1)=1.00,
whereas f(2)=1.74, meaning that 2 elves will only be able to wrap 1.74
as many gifts as 1 elf, instead of twice as many.
Can we help Santa decide how many Elves to hire? And can we figure out how much
the Krampus shenanigans are costing Santa, Inc.? We certainly can, and today we
will do so using the goalSeek feature which we just added to Quipu.
More...
22 Oct 2025
I came across this post on the fediverse the other day, pointing to an
interesting article explaining the CVM algorithm. I found the
algorithm very intriguing, and thought I would go over it in this post, and try
to understand how it works by implementing it myself.
The CVM algorithm, named after its creators, is a
procedure to count the number of distinct elements in a collection. In most
situations, this is not a hard problem. For example, in F#, one could write
something like this:
open System
let rng = Random 42
let data = Array.init 1_000_000 (fun _ -> rng.Next(0, 1000))
data
|> Array.distinct
|> Array.length
val it: int = 1000
We create an array filled with 1 million random numbers between 0 and 999,
and directly extract the distinct values, which we then count. Easy peasy.
However, imagine that perhaps your data is so large that you can’t just open it
in memory, and perhaps even the distinct items you are trying to
count are too large to fit in memory. How would you go about counting the
distinct items in your data then?
The CVM algorithm solves that problem. In this post, we will first write a
direct, naive implementation of the algorithm as presented in the paper, and
try to discuss why it works. Then we’ll test it out on the same example used in
the article, counting the words used in Hamlet.
More...
08 Oct 2025
I was thinking recently about ways to combine prediction models, which lead
me to the Softmax function. This wasn’t my first encounter with it (it
appears regularly in machine learning, neural networks in particular), but I
never took the time to properly understand how it works. So… let’s take a
look!
What is the Softmax function
The Softmax function normalizes a set of N arbitrary real numbers, and
converts them into a “probability distribution” over these N values. Stated
differently, given N numbers, Softmax will return N numbers, with the
following properties:
- Every output value is between
0.0 and 1.0 (a “probability”),
- The sum of the output values equals
1.0,
- The output values ranking is the same as the input values.
In F#, the standard Softmax function could be implemented like so:
let softmax (values: float []) =
let exponentials = values |> Array.map exp
let total = exponentials |> Array.sum
exponentials |> Array.map (fun x -> x / total)
More...
26 Sep 2025
In my previous post, I took a look at handling the selected item in an
Avalonia ListBox with FuncUI, so the ListBox properly reflects what item
is currently selected, based on the current State. In this post, I will go
into another aspect of the ListBox that gave me some trouble, handling dynamic
updates to the list of items. Once again, this post is nothing particularly
fancy, and is mainly intended as notes to myself so I can remember later some of
the steps I took.
First, what do I mean by dynamic updates? The examples in the FuncUI docs
go over displaying a list of items that do not change. However, in many real
world applications, you would want to be able to change that list, in a couple
of different ways:
- adding or removing an item,
- editing the selected item,
- filtering the contents of the list.
While editing an item is not particularly complicated in general, and follows
the standard Elmish / MVU pattern, one case that tripped me up was editing an
item in a fashion that impacts how it is rendered in the list, such as changing
the display name of the item. I will go over the solution I landed on, but I am
not sure this is the best way to do it, so if anybody can suggest a better
approach, I would be very interested in hearing about it!
Anyways, let’s dig into it, and build a simple example illustrating all of
these features. The final result will look something like this, and, in case
you are impatient, you can find the full code example here.

We’ll start from where we left off last time,
with a State that contains a collection of Items, and the currently
selected item:
type Item = {
Id: Guid
Name: string
}
type State = {
Items: Item []
SelectedItemId: Option<Guid>
}
More...