Continuous Releasability: How to Build Software Without Blocking the Next Release

12 min read

Software is never really finished.

Even mature systems are constantly being renovated. Bugs are fixed, internal structures are replaced, new capabilities are introduced, and old assumptions are reconsidered. The difficult question is not whether development should continue. It is how to keep improving a system without making the entire thing unusable while the work is underway.

I have been thinking about this question in the context of software versioning and release management, particularly in long-running scientific software projects such as RuFaS, the Ruminant Farm Systems model. But the underlying principle applies to nearly any software project:

Incomplete work may be acceptable in the main development branch. Unreleasable work is not.

That distinction matters.

A team should be able to make progress on large changes without waiting months to integrate them. At the same time, the presence of unfinished work should not prevent the team from releasing everything else that is ready.

Think of the Software as a House

Imagine that you live in a house that needs continuous renovation.

You want to replace the kitchen, rebuild a bedroom, update the electrical system, and perhaps add a new room. These are worthwhile improvements, but you also need to continue living in the house.

More importantly, you still want to be able to invite people over.

A software release is a little like hosting guests. You are opening the current state of the system to other people and saying, “This is ready for you to use.”

Now imagine beginning every renovation at once.

The kitchen is disconnected. The flooring has been removed from the living room. Tools are scattered across the hallways. The electricity works in some rooms but not others. There is dust everywhere, and several areas are unsafe to enter.

Every renovation may eventually improve the house, but the house as a whole is no longer ready for guests.

This is what happens when unfinished software work spreads across the entire development branch. Even when the team has completed valuable bug fixes, documentation, or smaller features, it cannot produce a release because another large initiative has left the shared branch in an unusable state.

A better approach is to isolate the renovation.

One room may be under construction behind a closed door. The walls may be open, the floor may be unfinished, and the work may take weeks. But the kitchen still works. The living room is clean. The hallways remain safe.

The house can still receive guests.

The room is incomplete, but the house is not unusable.

That is the difference between work in progress and release-blocking work.

Continuous Releasability

Many teams talk about continuous integration. Changes are merged frequently so that developers do not spend long periods working against an outdated version of the codebase.

A related principle deserves equal attention: continuous releasability.

Continuous releasability does not mean publishing every commit. It does not mean every experimental idea must immediately become available to users. It also does not mean the development branch contains only finished features.

It means that active development is structured so the team can still prepare a release when needed.

A continuously releasable development branch should not require the team to:

  • revert an unrelated feature before releasing;
  • selectively remove commits;
  • wait for a months-long project to finish;
  • accept broken behavior because more work is supposedly coming later;
  • maintain unnecessary differences between development and release branches;
  • perform a large emergency cleanup every time a release is planned.

The development branch can contain rooms under construction. It should not resemble a house that has been demolished all at once.

Dormant Code as a Closed Door

One way to support gradual development is through dormant code.

Dormant code is new code that exists in the project but is not yet part of the normal execution path. It may represent a future subsystem, a replacement implementation, a new calculation method, or groundwork for a larger architectural change.

The important point is that users do not depend on it yet.

The existing pathway continues to operate while the new pathway is built and tested alongside it. When the new implementation is ready, the software is deliberately routed through it.

The closed door in the house analogy represents this boundary.

The room under renovation is physically part of the house. The work is visible to the owners and can be inspected regularly. But guests do not accidentally walk into exposed wiring because the unfinished area is intentionally separated from the rest of the home.

In software, that separation might be created through:

  • a feature flag that is disabled by default;
  • a separate module that is not yet called;
  • a configuration-controlled pathway;
  • an internal interface with the existing implementation still selected;
  • a new service or component that is developed independently;
  • dependency injection that allows the new implementation to exist without becoming the default.

Dormancy should be structural. It should not depend on someone remembering not to call a half-finished function.

The unfinished pathway should not accidentally alter existing results, create new required configuration, change output formats, or break normal execution.

Incomplete Does Not Mean Careless

Allowing incomplete work into a shared development branch does not mean lowering engineering standards.

Dormant code still needs to compile. It should still pass the relevant checks. Its internal components should be tested to the extent that their current design allows. Most importantly, the existing behavior of the system should remain protected.

This is especially important in scientific software.

A partially implemented calculation pathway must not silently affect scientific outputs. A new data structure should not change results simply because it has been added to the repository. An unfinished replacement model should not become active through an undocumented configuration setting.

Existing users should continue receiving the documented behavior until the new pathway is deliberately enabled.

A good rule is:

New work may be incomplete internally, but the current supported behavior must remain complete externally.

Dormant code is not Abandoned code

Dormant code should also have a clear purpose. It should have an owner, a related issue or project, and a plausible path toward completion or removal.

Otherwise, dormant code can become abandoned code.

A house should not contain a permanently locked room that no one remembers renovating.

The Problem with Long-Lived Parking Branches

The alternative to gradual integration is often a long-lived feature branch.

The team begins a major project on a separate branch and keeps it there until everything is finished. This may seem safer because incomplete work stays away from the main development branch.

But separation creates its own risks.

Return to the house analogy.

Suppose that instead of renovating a room inside the house, you build the replacement room somewhere else. You use the measurements from the house as it exists today, then spend six months completing the room at another location.

During those six months, the original house changes.

A hallway is widened. Plumbing is moved. The electrical system is updated. The floor height changes. A supporting wall is relocated.

When the completed room is finally brought back, it no longer fits.

The doorway does not align. The pipes do not connect. The floor is at the wrong height. The electrical connections use an old standard.

The room may be beautifully built, but attaching it to the current house becomes a major construction project.

This is what happens to a long-lived branch.

While one group continues building in isolation, the main codebase evolves. Shared interfaces change. Other components are refactored. Bugs are fixed. Assumptions shift.

When the large branch finally returns, the team must integrate months of work in a single event.

Merge conflicts are only the most visible problem. The deeper challenge is conceptual divergence. The new code may have been built for a version of the system that no longer exists.

Large integrations are also difficult to review. A reviewer can meaningfully evaluate a new interface, a data structure, or a small internal component. It is much harder to thoroughly review an entire replacement subsystem introduced through one enormous pull request.

Gradual integration allows the room to evolve with the house.

Conflicts appear earlier. Design problems are discovered sooner. Each step is smaller and easier to review. The team does not have to wait until the end to learn that the new work does not fit.

Small Changes, Large Outcomes

A substantial feature does not have to arrive as a single substantial merge.

It can be introduced through a sequence of smaller changes:

  1. Define the new internal data structures.
  2. Introduce supporting utilities.
  3. Create the interfaces between the new subsystem and the existing system.
  4. Add isolated components and their tests.
  5. Build a new implementation alongside the current one.
  6. Add a controlled mechanism for selecting between implementations.
  7. Validate the new pathway.
  8. Document its behavior and limitations.
  9. Activate it when it is ready.
  10. Remove the old implementation later, when appropriate.

Each change leaves the project functional.

The team receives feedback throughout the process rather than only after the feature is complete. Architectural disagreements emerge while they are still inexpensive to resolve.

This approach also improves accountability. It is easier to understand what each change contributes and easier to identify which change introduced a problem.

When the Door Opens

A dormant feature becomes an active feature when the normal software workflow begins using it.

That transition should be intentional.

Before opening the door, the room should be inspected. The electrical outlets should work. Construction materials should be removed. The floor should be safe. The room should be furnished for its intended use.

In software, this means the feature has been tested, validated, documented, and integrated into the normal execution path.

For scientific systems, activation may also require confirming how the new implementation affects outputs and whether differences from the previous pathway are scientifically expected.

Once the feature is active, users can reasonably rely on it. It is no longer an internal experiment. It has become part of the supported product.

That is also the moment when it should be advertised as a completed feature.

Work in Progress and the Changelog

Changelogs become complicated when a release includes dormant work.

Technically, the code may be part of the release. New classes, interfaces, tests, or internal structures may now exist in the repository. But users still cannot rely on the complete feature.

The changelog should distinguish between internal progress and available functionality.

A work-in-progress entry might say:

Added internal architecture for a future data-processing pathway. The new implementation is not yet used by default and does not change current outputs.

Later, when the feature is activated, the changelog might say:

Enabled the new data-processing pathway, including supported configuration, validation, and user documentation.

These statements describe two different events.

The first says construction has begun.

The second says the room is open.

Without this distinction, a changelog may create expectations that the software cannot yet satisfy. The mere presence of code should not be presented as the availability of a supported feature.

This does not mean internal progress should be hidden. It means it should be described accurately.

Why This Matters for Versioning and Patch Releases

In semantic versioning, a patch is simply the final number in a version such as 2.3.1. It usually represents a backward-compatible bug fix or small correction.

There is nothing inherently wrong with patch version numbers.

The operational difficulty appears when a bug must be fixed on a stable branch because the development branch contains unfinished work that cannot be released.

The stable branch receives the fix, but the development branch has moved elsewhere. The team must then merge, cherry-pick, or recreate that fix in the development branch.

If this happens repeatedly, the branches begin to represent different histories. Conflicts become more common, and teams spend time reconciling changes that should have followed one path.

The underlying issue is not the existence of a patch release. It is the inability to release from the normal development flow.

When the development branch remains continuously releasable, most fixes can follow the standard path from development to testing to release. Emergency hotfixes may still be necessary, but they become exceptions rather than a routine consequence of an unstable development branch.

In my work with RuFaS, this has been one of the central concerns around release strategy. RuFaS is a large, multidisciplinary scientific software project. Some changes may involve extensive scientific and engineering work that cannot be completed in a single development cycle.

The project should not have to choose between integrating that work early and making releases impossible, or isolating it for months and accepting a painful integration later.

The better goal is to introduce the change gradually, keep it dormant where necessary, and preserve the ability to release the rest of the system.

Not Every Room Needs to Be Finished

The deeper idea is cultural.

Developers should consider release readiness as part of the design of a change, not as a cleanup task performed before publication.

The relevant question is not only:

Does this new code work?

It is also:

Can the rest of the system continue working while this code is unfinished?

And:

If we needed to release tomorrow, would this change prevent us?

This does not discourage ambitious work. It makes ambitious work easier to absorb.

It allows teams to integrate early without exposing unfinished behavior. It reduces the risk of long-lived branches without turning the shared development branch into a construction site. It supports frequent releases without pretending that every feature can be completed quickly.

Software development will always involve rooms under renovation.

The goal is not to finish every room before inviting anyone inside.

The goal is to keep the house safe, dependable, and welcoming while the renovation continues.

I also shared a short video reflection on this idea here: Continuous Releasability: How to Build Software Without Blocking the Next Release.

Continue Reading

Next Article

6 Truths About Software Every Business Leader Should Know

The real costs and leadership work of software begin after launch. Here are six truths about maintenance, technical debt, and protecting future velocity.

December 11, 2025

Stay Informed About New Work

Subscribe to receive updates when new projects, photographs, music, or writing are released.

You can unsubscribe at any time. Your information will never be shared.