The value of sensemaking

Discovering and interpreting beliefs in groups

Ever been part of a conversation that seemed to revolve around the same issue over and over again? Or a meeting where some people keep diving into details and others want to stay more high-level? As consultants, we often come across these kinds of sessions. We noticed that these situations can lead to tension, slowing down and (healthy) conflict. Over time, we learned how to tackle these situations by using sensemaking, a technique that offers us the right insights to guide a workshop or decision-making. 

What do we mean with sensemaking?

Sensemaking is what the word says, making sense of the environment. Its purpose is to get an understanding of the group and elements in play. These could be perspectives, opinions, preferences, stakes and/or beliefs. We search for the things that are not said. Gut feelings, worries or other forms of concerns. Making these concerns explicit and visible provides valuable information, helping the group make the right decisions or get to effective action. For us, as facilitators, it means making sense of the thoughts, opinions and emotions of the group. Let’s clarify with an example.

Read more →

Compile-safe builder pattern using Phantom Types in Scala

Scala logo

In this short article, we create a classic builder pattern to demonstrate the power of Phantom Types. Phantom Types provide extra information to the compiler, so that it can introduce extra constraints and check whether they hold at compile time. The program will fail to compile if one or all of the constraints don’t hold. Thus, you can prevent running into costly runtime issues by leveraging Phantom Types. Finally, as a bonus, they do not come with extra runtime overhead, as the Phantom Types are needed only for compilation, and are ultimately erased from the bytecode.

Builder pattern in Java

Firstly, let’s have a look at the builder pattern in Java:

public record Person(String firstName, String lastName, String email) {}

The builder itself could look something like this:


public class PersonBuilder {
    private String firstName;
    private String lastName;
    private String email;

    public PersonBuilder firstName(String firstName) {
        this.firstName = firstName;
        return this;

    private PersonBuilder lastName(String lastName) {
        this.lastName = lastName;
        return this;

    public PersonBuilder email(String email) {
        return this;

    public Person build() {
        // Optionally validate and throw exceptions for missing input
        return new Person(firstName, lastName, email);

When the build method is called, there’s no way to guarantee that all the fields of the Person are actually specified. In the above code, that can only be determined during runtime.

Person person = new PersonBuilder()
                      .build(); // Oops, we forgot to specify the email

Surely, you could introduce exceptions to deal with missing input, but then the code is no longer referential transparent, as side effects could occur and you would need to deal with these as well.

An expression is said to be referentially transparent if it can be replaced by its value without changing the program’s behaviour.

You could decide to write more tests, as the behaviour is only apparent during runtime. However, there’s no need to do this if we can already prevent it in the first place by the Scala compiler. Why test something that is already guarded by the compiler?

Builder pattern in Scala

Let’s share the entire code and then go through it step by step:

import PersonBuilder.{Email, FirstName, FullPerson, LastName, PersonBuilderState}

case class Person(firstName: String, lastName: String, email: String)

object PersonBuilder {
  sealed trait PersonBuilderState

  sealed trait Empty extends PersonBuilderState
  sealed trait FirstName extends PersonBuilderState
  sealed trait LastName extends PersonBuilderState
  sealed trait Email extends PersonBuilderState

  type FullPerson = Empty with FirstName with LastName with Email

  def apply(): PersonBuilder[Empty] = new PersonBuilder("", "", "")

class PersonBuilder[State <: PersonBuilderState] private (
    val firstName: String, 
    val lastName: String, 
    val email: String) {
  def firstName(firstName: String): PersonBuilder[State with FirstName] =
    new PersonBuilder(firstName, lastName, email)

  def lastName(lastName: String): PersonBuilder[State with LastName] =
    new PersonBuilder(firstName, lastName, email)

  def email(email: String): PersonBuilder[State with Email] =
    new PersonBuilder(firstName, lastName, email)

  def build()(implicit ev: State =:= FullPerson): Person = {
    Person(firstName, lastName, email)

We can start using the builder as follows:

val person = PersonBuilder()
  .build // Oops, we forgot to specify the email

If you try to compile this code, you will not be able to:

Cannot prove that PersonBuilder.Empty with PersonBuilder.FirstName with PersonBuilder.* LastName =:= PersonBuilder.FullPerson

If we squint our eyes a bit, then it basically says:

(Empty with FirstName with LastName) != FullPerson

Which is correct, as our definition of FullPerson demands us to include an e-mail address:

type FullPerson = Empty with FirstName with LastName with Email

When you include the e-mail as well, you’ll see that everything compiles fine again:

val person = PersonBuilder()
  .email("") // By adding the e-mail, it will compile again

Code explanation

So how does this all work? In this paragraph, we go through the code and give more details on the working.

Firstly, we define a set of properties that we would like to use:

sealed trait PersonBuilderState

sealed trait Empty extends PersonBuilderState
sealed trait FirstName extends PersonBuilderState
sealed trait LastName extends PersonBuilderState
sealed trait Email extends PersonBuilderState

In the table below, you can see all the states that we have defined.

EmptyThe initial state (i.e. none of the properties have been set)
FirstNameThe first name is set
LastNameThe last name is set
EmailThe e-mail is set

Secondly, we’ll introduce a new type by specifying that a FullPerson is the combination of an Empty person with a FirstName plus a LastName plus an Email.

type FullPerson = Empty with FirstName with LastName with Email

Next, we create a Type Class where we use a generic and constraint it to be of type PersonBuilderState.

class PersonBuilder[State <: PersonBuilderState]

In addition, we extend the State for each of the methods, to provide additional type information to the compiler:

def firstName(firstName: String): PersonBuilder[State with FirstName]

It takes the State and extends it with FirstName. When we create a new PersonBuilder(), the State will just be Empty. After setting the first name, the State will now be Empty with FirstName. If afterwards we set the e-mail, the State becomes Empty with FirstName with Email. As you can see, the order doesn’t really matter. We only need to make sure that we have a FullPerson the moment we call the build method.

The last part of the puzzle is the actual build method itself:

def build()(implicit ev: State =:= FullPerson)

The magic symbol here is =:=. It is used for expressing equality constraints. In other words, the compiler needs to prove that State at the moment of executing the build method is a FullPerson. If the compiler cannot prove it, you’ll run into a compile error.


And that’s basically it! Phantom Types is a very powerful concept that can be used to make your code a lot more robust. They allow us to catch issues in the earliest stage possible, namely compile time, which is also the cheapest stage to fix issues.

5 Ways to Improve Software Quality

Many opinions exist on what software quality exactly is. Clearly defining the quality characteristics of software proves quite difficult. This holds from a functional perspective (‘it should just work!’), as well as from a non-functional perspective (‘the solution is slow…’). In this blog post, we will sketch the arena and provide five useful tips to define and improve software quality.

Talking about Software Quality?

The thing about software quality is that it is more difficult to verify its presence than it is to verify its absence.

For example, systems of poor modifiability cannot keep up with changing business requirements – these systems are often perceived as having insufficient quality. At the same time, we often tend to recognize the elegance of using appropriate architectural patterns or design patterns to solve a problem – unfortunately, software architects or engineers can get carried away and over-apply these.

At Qxperts, we build on a series of business indicators that allow you to make software quality tangible. We highlight two of them:

  • Improved time-to-market – a direct translation from the metric “Lead time to change”, as described in the DORA research; the time it takes to go from a commit in version control to making the result live and available for users.
  • Increased revenue and business value – software is only valuable if it is actually used in production. It is about the business opportunities that can be seized with the software – increased market share, product variety, et cetera.

These indicators are about speed and value. This requires a balancing act. Does your business prefer the right software now as opposed to better software tomorrow? When is good actually good enough? Nobody wants to end up with poor quality software that breaks all the time, nor does anyone want to gold-plate their solution before putting it to the test in production.

Read more →

How to start contributing to OSS

Many of us, software engineers, at some point in time decide that we would like to get involved with an open-source project. Many of us indeed become contributors, but it’s not always clear where to start and how to find a project that would be a perfect match. For some years these questions were holding me back from starting, and I thought that possibly I’m not the only one. So I decided to share my experience and hopefully it saves you some time or frees you of unnecessary frustrations.

Read more →

The three forms of CI/CD

Many models and perspectives describe the complexity of an organization. In the end, it all boils down to people interacting with technology using processes. Sociologist Dr. Ron Westrum provided a typology of organizational cultures, including a generative type. Key characteristics for a generative culture include:

  • Oriented to performance
  • High cooperation
  • Failure leads to inquiry
  • Share risks

Encouraging these characteristics in the culture of an organization allows capacity, quality and innovation to grow. A CI/CD pipeline aids in facilitating the tools and processes to make these characteristics possible. A CI/CD pipeline is often perceived as a way to bundle and distribute software. It is the modern way to deploy software using automation instead of doing it manually. From a technical perspective, this is valid. Yet, the key concept of a CI/CD pipeline is to provide consistent quality and early feedback. By continuously monitoring the existing (quality) performance, you gain insights & data on how to improve deployments and the controls being applied.

Using the triad of people, process and technology, let us see what a CI/CD pipeline means for an organization and how it impacts the quality of the software delivery process.

Read more →

I want a mock API and change responses on runtime

Nowadays many web applications are Single Page Apps that connect to an API via HTTP (for example REST or GraphQL). When you develop such an application, you do not only have to run a local development server, but also an API where it connects to. The API could be running on a dev cluster, but you can also run it locally on a different port for example.

A big advantage of connecting to a real API, is that you probably create less bugs. On the other hand, it is sometimes hard to test or setup up all possible scenarios. How do you for example test, in a controlled way, that your API responds with an internal server error and how the client handles this? In this blog I explain how to do this.

Read more →

Generic ListItem in React Native using TypeScript

When using React Native, lists are created using the FlatList component. A FlatList is constructed using one or more repeatable list items, one item per row. In this blog I’ll demonstrate how TypeScript can help to create FlatLists with generic list items.

This is a technique where we can reduce boilerplate, but still have readable and customisable FlatLists.

Let’s dive right into the basics of a FlatList by creating a list of StarWars characters.

Read more →

Structured concurrency: will Java Loom beat Kotlin’s coroutines?

Kotlin and Java Loom: structured concurrency for the masses

Java, as slow adopter of new concepts is getting structured concurrency as part of the Loom project. This addition enables native support for coroutines (termed virtual threads) in Java. What does that mean for Java & Kotlin developers? Should we now all transform our existing code bases to support virtual threads? Or even jump back to Java?  We compare two approaches to do structured concurrency on the JVM: Java Loom’s virtual threads and Kotlin’s coroutines. As it turns out, Kotlin coroutines show to be more developer friendly, while Java Loom might have its sweet spot as part of Java libraries and frameworks. Want to know why? Buckle up, as we have a lot to talk about!

Read more →

Monitoring consumer lag in Azure Event Hub


Consumer lag is the most important metric to monitor when working with event streams. However, it is not available as a default metric in Azure Insights. Want to have this metric available as part of your monitoring solution? You can set it up with some custom code. In this blog we show you how.

Read more →