A programmer’s field guide to assertions

Intended audience: Programmers working on long-lived programs which use bounds-checking for array/vector/slice accesses and interested in making their programs more robust.

An assertion in code is a statement of some condition holding true at a particular point in the program. If the condition does not hold true, then the program’s normal control flow is interrupted.

Common ways of handling an assertion being “tripped” (analogous to a tripwire), include throwing an exception, panicking, terminating the program, or, more rarely, logging a message at an appropriate severity level.

Assertions may be conditionalized on the build configuration:

Assertions are generally provided as language built-ins, macros or library functions.

Assertions are sometimes associated with Design by Contract (aka ‘programming by contract’). This post is not about Design by Contract. Specifically, this post is about using assertions as one potential tool in your toolbox, not about designing your program in a specific way.

Assertions are best used as a complement to other techniques of writing robust software, such as writing tests and running them in CI, capturing distinct sets of invariants using dedicated types, and so on.

In this post, I want to share my experience using assertions across a variety of codebases, advocating for them at work, and how you could be more successful at driving adoption of assertions relative to me. Here is the overall structure of the post:

  1. My history with assertions: Here I describe how I’ve been using assertions from 2019-2025 across a variety of codebases, with others and by myself, and a mix of successes and failures.

  2. Practical examples of assertion use: I give some day-to-day examples of using assertions taken from work, slightly simplified, but still grounded in sufficient context so that it can give you some ideas. The three main examples I give are:

    If you’re already comfortable with writing assertions, you can skip this section.

  3. How to use assertions effectively: I cover my current, high-level beliefs on how assertions ought to be used.

  4. Notes on organizational adoption: This section has advice for people who want to advocate for the use of assertions, or changing how assertions are used, at work. This section is primarily meant for junior developers, as well as developers not used to driving changes across lots of teams.

    This section depends on some of the history in the first section.

  5. Common objections to assertions: I discuss some common objections that come up when advocating for assertions, as well as potential responses to them.

Let’s get started.

My history with assertions

Assertions in swiftc (2019-2021)

When I worked on the Swift compiler (written in C++), I got used to using the standard C assert macro, which is enabled in local development builds, and expands to a no-op in production builds.

Typically, these would be written as:Yes, this means that the default way of doing assertions only supports having a fixed message; trying to add more contextual debugging info requires more fiddling.

assert(condition && "some fixed message here");
// Works because string literals are of type const char *
// and pointers support implicit conversion to booleans
// based on null-ness.

We would semi-frequently get issues where a compiler crash report would not have a ton of useful information, but when running the example code under a development build compiler, it would immediately trigger an assertion, pinpointing the bug. This led me to believe that for reported crashes which did not have example code (which we got many of), it was likely that enabling assertions by default, or publishing a toolchain with most assertions enabled by default, would be impactful in improving our ability to triage bugs.

I spent some time profiling the existing assertions, and if I remember the numbers correctly, I found that excluding the top 10 most expensive assertions in the swiftc codebase, while keeping the rest enabled (about 10K at the time), would increase the compiler’s end-to-end runtime by about 3% relative to a no-asserts build.You might be thinking “3% overhead is a LOT.” And yes, depending on the context, it might be a lot. But you can imagine that if you want to lower the overhead further, you can disable more assertions until you’ve gotten under your overhead budget.

Based on the numbers, there was some interest in enabling assertions in release builds, but I was not able to successfully advocate for turning on most existing assertions in production, before leaving Apple in late 2021.

Since the swiftc codebase is open source, we can still go look at it. Turns out, things have changed for the better in the past few years. In May 2024, Tim Kientzle added initial support for always-on assertions. As of Aug 2025, there are about 12.8k uses of the old assert macro and about 1k uses of the new Swift-specific ASSERT macro.

Assertions in scip-ruby (mid 2022-2024)

At Sourcegraph, one of my early projects was scip-ruby, which is a Ruby indexer forked from Stripe’s typechecker Sorbet (written in C++). Sorbet’s assertions are interesting (code). There are basically 3 macros for debug assertions:

Sorbet’s production builds do not enable assertions.

When working on scip-ruby, we’d see issues with crashes for specific code patterns which we had not quite accounted for in the indexing logic. These issues would be hard to debug because we often did not have access to customer code (e.g. customers running indexers on-prem).

The work on scip-ruby started around June 2022. In July, I started publishing separate binaries with assertions in both scip-ruby and Sorbet enabled, to help debug issues.

Assertions in scip-clang (2023-2024)

After scip-ruby, in Dec 2022, I started work on scip-clang, an indexer for C and C++ which uses Clang as a library. The scip-clang codebase can be viewed as a cousin of scip-ruby, using Bazel for builds, using the spdlog library for logging, and so on.

On Jan 1 2023, I ported over a minimal version of the ENFORCE macros from the scip-ruby/Sorbet codebase to scip-clang.

scip-clang’s initial development took a few months. After getting an MVP ready, during the initial bring-up phase for scip-clang, the very first customer reported issues with crashes when certain files were processed. scip-clang deliberately uses a multiprocess architecture to reduce the blast radius of crashes, but problems when processing widely used headers could still lead to indexing not working for a large number of files.

So in April 2023, I changed the release build to enable only scip-clang’s own assertions with a comment in the build configuration “Temporarily to smoke out issues on large codebases”. I also started publishing ‘dev’ binaries which additionally enable assertions inside LLVM/Clang as well as sanitizers.

The initial ~3 customers who tried scip-clang ran into the majority of customer-reported crashes in the history (so far) of the codebase.

Later, in mid-2025, I rediscovered the “Temporarily …” comment. Turns out, scip-clang has been running with assertions in release builds all this time. I left the build configuration as-is, because customers are generally running scip-clang once every few hours (or once a day), so while there is some slowdown, the benefit of having more useful crash reports is more than worth it.

Assertions in the Sourcegraph monorepo (2025)

In 2024, I learnt more about the use of assertions in SQLite as well as TigerBeetle and Antithesis.

I generally don’t take time off around Christmas, since I can do more focused work at the time, and I don’t really have any special events going on at the time. Around Christmas 2024, I decided to write a Go style guide for the Sourcegraph monorepo, where the web backend is implemented in Go. I largely wrote this by myself, with some feedback from one colleague who was also not taking time off.

Initially, I socialized the style guide among a group of tech leads, and then shared it more broadly. The style guide was largely received well. One point in it, which did not get a particularly warm reception, was the one titled “Use assertions for contract violations.”

It’s worth noting here that Go does not have native support for assertions, and the Go FAQ mentions that this is a deliberate omission:

Why does Go not have assertions?

Go doesn’t provide assertions. They are undeniably convenient, but our experience has been that programmers use them as a crutch to avoid thinking about proper error handling and reporting. Proper error handling means that servers continue to operate instead of crashing after a non-fatal error. Proper error reporting means that errors are direct and to the point, saving the programmer from interpreting a large crash trace. Precise errors are particularly important when the programmer seeing the errors is not familiar with the code.

We understand that this is a point of contention. There are many things in the Go language and libraries that differ from modern practices, simply because we feel it’s sometimes worth trying a different approach.

When I wrote the style guide, I was well aware of this, so I spent time writing a lengthy rationale sub-section. In hindsight, I do not believe that this actually helped that much.

After a few rounds of back-and-forth, in the end, we ended up with marking the section as ‘Experimental’.

In terms of the API shape, we ended up with something like the following:Some signatures have been simplified slightly.

func AssertPrecondition(cond bool, msg string, args ...any)
func AssertInvariant(cond bool, msg string, args ...any)
func AssertPostcondition(cond bool, msg string, args ...any)
func AssertUnreachable(msg string, args ...any)
func PanicUnknownEnumCase[A any](a A)

func DebugAssertionsEnabled() bool

func CostlyAssertionsEnabled() bool
func AssertPreconditionCostly(cond bool, msg string, args ...any)
func AssertInvariantCostly(cond bool, msg string, args ...any)
func AssertPostconditionCostly(cond bool, msg string, args ...any)

Some brief notes about the API design:

Since then, I’ve been using assertions a bunch when writing Go code. Recently, I created a large PR, where I re-implemented some large chunks of logic. While iterating on changes, I hit about 5~6 different assertions that I’d added as part of the change, one after the other. And once those assertions were fixed, and the code actually got to the tests, the tests, which themselves were several hundred lines, were mostly passing.

So has the project to use assertions in the Sourcegraph codebase been a success? No, it’s largely been a failure. As of Aug 2025, in the Sourcegraph monorepo, we have about 300 assertions, out of which I myself have written about 290. This was not the goal I started out with.

Reflecting on these numbers, I guess they make sense in some ways. Generally, the best place to make more new code match up with the style guide is through code reviews. But in the past few months, I haven’t reviewed a lot of cross-team PRs. And just because something is written in the style guide, and has a few toy examples, that doesn’t mean that it’s obvious to people how they should use that specific suggestion in their context.


Now that you’ve read about my past experience, hopefully you can more accurately judge how believable I am in the following sections.

Let’s talk about some practical examples of how one can use assertions.

Practical examples of assertion use

Strictly monotonic map and set construction

When transforming between data types, one common operation is to aggregate a list/array/iterator of key-value pairs into a map (or values into a set), for faster lookups.

If the reason for constructing the map or set is just for faster lookups, perhaps you already have the guarantee that the input sequence has no duplicates. In such a case, when doing the insertion operations as part of the set/map construction, depending on the language, you can assert that the construction is strictly monotonic in a few different ways:

If your language allows, and extending standard types is not frowned upon, you can potentially extend the default map/set type to have a dedicated method insertNew or similar which handles this.

Alternately, if you have a standard module/package where you have helper functions related to collection types, you can create standalone functions which capture the above patterns.

Scanning results from database queries

In my experience, database queries often come with implicit sets of guarantees which are often not precisely articulated. The more complex the query, the more complex these guarantees can be.


Recently, I wrote a SQL query at work which involved getting (to simplify a bit) the list of code repositories which contained the definitions of some particular symbols (think class ABC, method ABC.hello() etc.) and the references for particular symbols.

Each row returned by the query had the following structure:If you’re confused by the int[], that’s based on Postgres’s native support for array data types.

The query was quite long, and made the following guarantees:

  1. Each repository_id appears at most once in the result set.
  2. Each individual array of *symbol_ids did not have duplicates if a particular input array to the query did not have duplicates.
  3. For each row, at least one of definition_symbol_ids and reference_symbol_ids must be non-null and non-empty.This is in the context of code navigation actions like Go to definition and Find references. Repositories which do not mention any of the symbols of interest are not of interest from the point of view of code navigation.

Out of these, point 1 and point 2 are covered by the previous example of monotonic construction. For point 3, I added a separate assertion for each scanned row.


Another situation where this comes up is with queue-like tables, or more generally, tables where rows have various state transitions. You may have a state column, and the NULL-ness of other columns will be dependent on the exact state value. When scanning such dependent columns, it can be useful to assert that the value is not NULL for a particular state.

Test coverage with ‘sometimes assertions’

A ‘sometimes assertion’ is an assertion which combines the notions of assertions and test coverage. The basic idea is to check the following two conditions:

If yes, then at the end of running the test suite (not a single test), the test suite passes. Otherwise, the test suite fails as a whole.This means that sometimes assertions cannot be verified if only running a subset of the tests.

Have you ever had the situation where a particular condition is commonly true, but in the rare case it is false (or vice-versa), and it took you a bunch of time to figure out how to write a test to exercise the handling for the false code path? In such a case, you can use a sometimes assertion to make sure that someone doesn’t accidentally regress the coverage of the test suite.

Here’s a simple implementation of sometimes assertions in TypeScript based on one I recently introduced in a small work project:For more curious people, you can see the pull request which introduced this.

// Seen represents the aggregate of values
// we've seen so far for a particular conditional.
enum Seen {
    AlwaysFalse = 'always false',
    AlwaysTrue = 'always true',
    Mixed = 'mixed',
}

function combine(a: Seen, b: Seen): Seen {
    if (a === b) return a;
    return Seen.Mixed;
}

const _sometimesResults =
    new Map<
        string, // unique call-site key
        Map<
            string, // test name
            Seen    // combined values
        >
    >();

let _currentTestName = ''; // global
function setCurrentTest(name: string): void { ... }

The global map tracks the test name so that you can more easily understand which test exercises a particular code path in a particular way, without needing dedicated tooling for code coverage. Tracking the test name can be omitted in a more minimal implementation.

Here is the main assertion function:

// @param key uniquely identifies the call-site
function assertSometimes(cond: boolean, key: string): void {
    if (!_isTest) return; // global variable

    const seenByTest = _sometimesResults.get(key);
    if (!seenByTest) {
        seenByTest = new Map();
        _sometimesResults.set(key, seenByTest);
    }

    const testName = _currentTestName;
    if (testName === '') {
        throw new Error('Forgot to call setCurrentTest()?');
    }
    const prev = seenByTest.get(testName);

    const result = cond ? Seen.AlwaysTrue : Seen.AlwaysFalse;
    if (prev === undefined) {
        seenByTest.set(testName, result);
        return;
    }
    const combined = combine(prev, result);
    seenByTest.set(testName, combined);
}

Finally, here’s the function you invoke when the test suite finishes running:

function checkSometimesAssertions(): boolean {
    let fail = false;
    for (const [key, seenByTest] of _sometimesResults) {
        let agg: Seen | undefined;
        for (const state of seenByTest.values()) {
            agg = agg === undefined
                ? state
                : combine(agg, state);
        }
        if (agg !== Seen.Mixed) {
            fail = true;
            console.error(`missing test coverage: condition ${key} is ${agg}`)
        }
    }
    return fail;
}

Shout out to the Antithesis docs, where I originally got this idea.Antithesis was co-founded by the creators of FoundationDB, which has historically been one of the most battle-tested databases. Turns out, SQLite, which is also one of the most battle-tested databases, also has a similar form of assertions (a testcase() macro in SQLite terminology). Not a coincidence, perhaps?


Having talked about some potential examples of using assertions, let’s talk about some broader ideas on how to use assertions.

How to use assertions effectively

Note that while these are my personal rules of thumb, you may want to adapt them to your context based on experience.

Rule 1: Only assert properties you are 100% confident about

If you’re writing a production assertion, you should be 100% confident of it.

If you’re writing a debug assertion, you should probably be at least 90% confident. However, this confidence threshold can vary based on how many people are exercising the code paths you’re tweaking in their development environment.

Having 100% confidence about some property holding true can be a high bar, especially for widely used code. This can require auditing a lot of code to check whether the property holds true or not (e.g. if there are lots of writes to a particular field). But you still need to do this work. If you can’t do that, add a TODO, or follow whatever convention your codebase has for tracking small pieces of work, and use standard error handling for unexpected cases.

Similarly, when working with legacy code,I particularly like Michael Feathers’s definition of legacy code as “code without tests.” Alternately, you could interpret this as code where you do not have a robust theory of the program (in the Naur sense).

it might be tempting to add production assertions with the assumption that it will help smoke out bugs. This assumption may be true. But it may also cause fires that overwhelm your team’s capacity. I understand this temptation; I’ve been there multiple times. And I’m telling you from experience, this is generally a BAD idea.

Adding debug assertions in legacy code is fine, sure. But debug assertions carry one hazard; the presence of a debug assertion for a long time can be a misleading signal. It can be tempting to fall into the trap of “well, this debug assertion is pretty old, so it seems unlikely that this situation can actually be hit in practice.” However, if you’re working in a typical codebase, the state space explored by your dev environment is probably not entirely representative of the state space in production, so you cannot really get to the 100% confidence threshold that’s required by just waiting for a long time.

For legacy code, generally, you want to start off with building a theory and test coverage first, before peppering it with assertions. Other than that, focus your energy on new code paths first, where you fully control the inputs and outputs.

Rule 2: Do not assert statements about the external world

Assertions should be focused on properties that are guaranteed by existing parts of the program that are under your control and you can rely on.

If you’re getting some data over the network from a system you don’t own, you should not assert something about that. Log something and/or return an error.

If within the same process, you’re using a large library written by someone else, and its development is out of your control, don’t assert statements about data you got from that library, unless the library explicitly makes that guarantee.I’ve been burned by this one particularly many times in the past, such as assuming that some fields must be “obviously” set, such as when looking at AST nodes in various compilers’ internals.

Log a warning and/or return an error. Yes, I recognize that this can be high friction.

If you’re reading some input in a command-line tool, don’t assert properties of the data or arguments. Return an error instead.

If you’re reading something from the database where the data has some invariants that are meant to be upheld by application code (and not the DB itself), and you’re 100% sure that:

In that case, you can assert properties of the data you read.

Rule 3: Assert properties that your code relies on

Say your code relies on an array to not have any duplicates, and already be sorted. Then assert that. If this is too expensive to run in production, and there is no alternate solution which is feasible (e.g. creating a dedicated type which captures this invariant is undesirable due to other factors), you may want to make this a debug assertion.

Don’t add assertions for properties that you think should hold true, but the code doesn’t actually rely on it.

When this comes into conflict with Rule 2 (“Do not assert statements about the external world”), Rule 2 takes precedence.

Some checks inserted by compilers, such as bounds-checks and null checks, are essentially assertions. Your code probably relies on these heavily; you generally want to avoid explicitly writing these kinds of assertions because they can significantly hinder code readability.

Rule 4: Pair with coarse-grained recovery for online systems

For online systems such as servers, you want to recover from assertions at a coarse-grained boundary, such as a request boundary, or a process boundary. For logic which happens outside a request, you still want to make sure there is a recovery mechanism in place. For example, maybe some other process will automatically restart yours (or restart your container) if a health check fails.

If you have an ARCHITECTURE.md file or similar, that can be a good place to identify what the main places of recovery are. This way, when new people write assertions, they can be more confident in knowing where the recovery happens.

Rule 5: Make them as easy to deploy as possible

Ideally, all of your assertions will be running all the time, just like you have bounds-checking running all the time.

The world is not ideal though. Maybe you have people worried about the overhead. Maybe you have people worried about things blowing up and getting paged in the middle of the night. Maybe it’s something else. The point is that these are valid concerns.

One compromise is to make assertions configurable at program startup. During startup, you can read an environment variable and configure whether assertions are enabled by modifying a global variable. Generally, a boolean check like that should be free in most situations thanks to branch predictors.The main exception to this is very hot loops, particularly ones where the compiler may be able to perform SIMD optimizations in the absence of the boolean check.

However, this may not be sufficient to bring the performance overhead down to a manageable level. The way assertions are implemented in most languages, you cannot really modify the implementation of assertions used by third-party libraries – it’s an all-or-nothing matter.This is why my blog post on error handling has a short section dedicated to customizable assertions.

For large applications which use third-party libraries, it is generally beneficial to be able to toggle your own assertions separately from those of third-party libraries.

Additionally, assertion APIs generally don’t distinguish between potentially costly assertions (O(N) or more expensive) vs likely affordable assertions (O(1), like null checks). Even if your codebase is using more specialized APIs, your dependencies may not be.

So one compromise you can make is to publish two different builds, the production build where just your assertions are turned on, and a separate build with all assertions turned on. The latter build should still have optimizations enabled, in order to minimize the performance overhead.


So far, we’ve covered the technical aspects of how to properly use assertions, and also, how not to use them. If you’re working by yourself, that’s enough, and you can stop reading here.

If you’re working in a larger organization, you likely have more hurdles to clear. Based on the challenges I faced at Apple and Sourcegraph, the next two sections cover my current thoughts on how to handle the organizational aspect of how to get assertions adopted broadly.

Notes on organizational adoption

Acknowledging the real organizational culture

I think an organization has at least two cultures. The first is the idealized one; this is the one in LinkedIn posts, in company handbooks, and maybe in your mind as “how we say things ought to be done.”

Then there’s the real one; this is what you encounter day-to-day, in code reviews, in Slack messages etc. In particular, this is the one which tries to defend itself when you try to push back against it.

When trying to effect organizational change, you’re probably asking yourself questions of the form of:

It is your choice as to when you’re answering these questions for yourself, whether you are answering these questions based on your real-world experience, or based on how you think things ought to work.

If you’re focused on the real culture, you may even ask yourself questions like:

One of my biggest errors was that I did not think hard about the real culture.

To be clear, I’m not passing judgement here. In the real world, people are often more constrained for time and have shorter attention spans than we’d like to admit. People (myself very much included) are also affected by various cognitive biases; these are tempting to omit when attempting to portray or envision an idealized world. So it’s understandable as to why these two cultures are different.

However, the question one has to ask oneself to be effective, I think, is “which of the assumptions that I’m baking in to the process are based on what I’ve observed vs how I think things ought to be?”

Ideas also require following up

The most natural place to encourage people to adopt specific coding patterns is during code review.

Since it was neither practical nor desirable for me to actually code review all the PRs for the monorepo, the next best thing would’ve been to make sure that all the other Team Leads were closely aligned on what situations would be appropriate to use certain coding patterns (like assertions) and how to encourage their use.

I did not do that. The overall experience of pushing against friction left me drained, so after the style guide was approved with adjustments, I went back to focusing on effort needed within my team, instead of spending time on cross-team outreach.

In hindsight, this was a mistake in terms of budgeting my energy and time, as well as wishful thinking that writing things down in a Notion doc and sharing them in Slack a few times would be sufficient for broad adoption.

Ideas often require following up for adoption. Don’t copy what I did.


Let’s say you’re aware of your organization’s real culture, and you’ve determined that you’re going to spend your time on advocating for assertions, or changing how assertions are used, such as changing most assertions from debug assertions to production assertions.

The next section covers some of the most common objections you’re likely to run into, and how you can respond to them.

Common objections to assertions

I’ve described some common objections in the following format:

It’s important that you recognize that the responses are not template replies that you can simply use in conversations. You actually need to think about whether the reply is justified (do the preconditions hold), and should your reply be unsatisfactory, whether you can actually commit to following up with risk mitigations.

Based on control flow

Objection: Assertions interrupt the normal control flow of the program, making it harder to reason about the program.

Precondition: Your code has well-understood recovery mechanisms in place that have been exercised in production. If you don’t have that, you need to figure that out first before thinking about assertions.

Potential response: When writing an assertion, by design, one should not need to reason about what happens if the assertion is tripped, because either the program terminates OR recovery happens at a coarse-grained boundary, which is the same as for other issues such as out-of-bounds accesses.

Potential mitigations: You can offer to:

Based on performance

Objection: Assertions add unacceptable overhead to production builds. They should be turned off in production.

Preconditions:

Potential response: In practice, we accept the overhead of bounds-checking for arrays and null checks for pointers because they ensure safety, and that is generally worth the overhead. Most assertions are likely to be in a similar bucket because they will be O(1) checks (such as checking the length of an array).

Potential mitigations: You can offer to:

Based on code readability

Objection: When the code is peppered with assertions, it’s hard to read the code.

Potential response: ‘Reading’ code is really about understanding. When attempting to understand code, one constantly needs to ask oneself questions of the form of “Is property X guaranteed to hold here for all possible/practical code executions? If so, why? Which code is responsible for upholding this guarantee?” More concretely, this can look like:

When you see an assertion which states that it’s a pre-condition, this makes it clearer that the responsibility for upholding a guarantee is on the caller.

When you see an assertion which states that it’s a loop invariant or a post-condition, this makes it clearer that the responsibility for upholding the guarantee is on the asserting function.

So assertions help increase clarity.

Additionally, standard techniques such as grouping common functionality in functions and classes/structs can still be used.

Objections based on risk

Objection: Using assertions in production increases the risk of code blowing up.

Precondition: You have a reasonably detailed understanding of the overall deployment process, as well as the incident handling process when things fail at different stages of deployment.

Potential response: Generally, all code changes carry risks, and assertions are no exception to that. Here are the concrete benefits that I think enabling assertions in production will bring: <insert benefits here>. Do you think these benefits potentially justify the added risk?

Potential mitigations: You can offer to reduce risk by:

Based on error handling philosophy

Objection: Some variant of the text written in the Go FAQ: “programmers use [assertions] as a crutch to avoid thinking about proper error handling and reporting. Proper error handling means that servers continue to operate instead of crashing after a non-fatal error. Proper error reporting means that errors are direct and to the point, saving the programmer from interpreting a large crash trace.”

Potential response: Assertions are meant to be a complement to, not an alternative to proper error handling.

For example, if there is some data the program receives from an external system, the program should return an error if the data does not satisfy the necessary properties. In such a situation, an assertion would be inappropriate.

On the other hand, code is generally not written to return an error every time an index into an array/slice/etc. is out-of-bounds. That would add a lot of verbosity. It makes sense to instead make sure that indexes are in-bounds by construction, and rely on bounds-checking for safety. An assertion should be used in this kind of situation, except that the property that is known to be true is domain-specific.

Potential mitigations: You can offer to:

Based on type-driven design

Objection: It is always better to encapsulate invariants into types rather than use assertions.

Potential response: Assertions offer a gradual way of making code more robust. They can be used in addition to using more fine-grained types. For example, if the same assertion is repeated in lots of places, it might make sense to capture the invariant using a fine-grained type.

Additionally, depending on the language’s type system, it can quickly get unwieldy to attempt to encode complex invariants in types.

Dedicated types also introduce more vocabulary that programmers touching the code need to learn. They may also require reimplementing/forward functionality from the underlying base type. So while they can be beneficial, they also have some downsides.


Hopefully, that gives you some idea of what the common objections are, and what potential follow-up work you may need to do to mitigate risks associated with each objection.

Depending on how much resistance you expect to face, you may want to begin by asking more questions first, instead of trying to reply with counterarguments when faced with objections.

Closing thoughts

Over the years, I’ve repeatedly found assertions valuable in debugging issues, clarifying my understanding of code, getting more value out of tests, and generally increasing my confidence in shipping code.

I hope this post helps you in using and advocating for the use of assertions at work.

If you haven’t used assertions much before, I recommend exploring open source codebases in languages you’re familiar with that use assertions a fair bit. Some examples of OSS codebases that I know of:Sorry, these examples are biased towards compilers and databases, since those are what I’m most familiar with. 😅

Happy asserting!

P.S. If you found this blog post helpful in some way, I’d love to know how it helped you. Do send me an email; I like hearing from people who read the blog.