ENGINEERING INSIGHTS · PRODUCT ENGINEERING

Build for the next release. And the one after.

How product engineering teams make each release answer a question: release briefs with decision rules, thin vertical slices, flags that separate deploying from releasing, and stable rollouts.

AUTHOR
NeuralSharks EngineeringEngineering team
AUTHOR EXPERTISE
  • AI engineering
  • Software architecture
  • Quality engineering
  • Cloud & DevOps
PUBLISHED
UPDATED
READING TIME
5 min1,082 words

01 / 06

The first release is an instrument

The first version of a feature is a bet placed with incomplete information. Its job is to deliver value, and also to tell the team which of its assumptions were wrong. A release that cannot answer that question has spent the team's time and taught it nothing.

That changes what "building it well" means. A well-built early release is one that can be measured, changed, or removed cheaply. Architecture still matters, but it serves a specific goal: keeping the ability to respond to what the team learns without turning each change into a rewrite.

02 / 06

Write down the question before the code

Before building, write a short release brief. It is a page, not a document, and it forces the conversation that usually happens too late: what would make us keep this, change it, or remove it?

release-briefs/bulk-import.yamlYAML
release: bulk-import-v1
question: >
  Do new workspaces with existing data activate faster
  when they can import it instead of entering it by hand?
leading_metric: share of new workspaces with 10+ records within 48 hours
guardrails:
  - support tickets tagged "import" per 100 imports
  - p95 import duration for files under 10 MB
audience: new workspaces, 50% rollout, assignment stable per workspace
decision_rules:
  keep: leading metric improves for the import group and guardrails hold
  rework: imports are started but rarely completed
  remove: no measurable difference after four weeks at full rollout
flag: bulk_import   # default off; doubles as the kill switch

Two parts do most of the work. The guardrails name what must not get worse, so that a feature that lifts one number by breaking another is caught. The decision rules are written before any data exists, which keeps everyone honest when results arrive and someone is attached to the outcome. Teresa Torres's opportunity solution trees are a useful way to connect each release question to the outcome it serves.

03 / 06

Slice vertically, and thinly

A thin vertical slice delivers one narrow path through every layer (interface, logic, and data) to real users. A horizontal slice delivers one layer completely: the whole import data model, then the whole parser, then the interface. Horizontal slices feel efficient and are not. Nothing reaches a user until the last layer lands, and by then the earlier layers encode assumptions nobody has tested.

For bulk import, the first slice might accept only CSV, for one record type, with fixed column mapping, listing errors rather than fixing them in place. That is enough to answer the brief's question. Excel files, custom mapping, and inline correction are the next slices, and the data may show that some of them are unnecessary. Basecamp's Shape Up describes the same instinct as fixing the time and varying the scope.

  1. 01Question

    Release brief: leading metric, guardrails, decision rules

  2. 02Thin slice

    One narrow path, end to end, to real users

  3. 03Flagged release

    Deployed dark, then enabled for a stable cohort

    Guardrail breached: flag off in seconds

  4. 04Measure

    Leading metric and guardrails, per cohort

  5. 05Decide

    Keep, rework, or remove, by the rules written first

    Remove: delete the code and the flag

FIG. 1Each release is one turn of the loop. The two exits are what keep a feature from becoming permanent by default.

04 / 06

Separate deploying from releasing

Feature flags let code reach production before users see it. That separation is what makes small, frequent deploys safe for a team working on one main branch, a practice DORA describes as trunk-based development, and it is what makes a percentage rollout possible. Pete Hodgson's taxonomy of feature toggles separates release, experiment, ops, and permissioning toggles. They have different lifetimes and owners, and mixing them up is how flag systems become unmanageable.

For rollouts and experiments, assignment must be stable: the same workspace should see the same variant on every request and every server, or the measurements mean nothing. Hashing the flag name with the subject's ID gives deterministic bucketing without storing any assignments:

flags/rollout.tsTypeScript
import { createHash } from "node:crypto";

/** Deterministic: the same flag and subject always land in the same bucket. */
export function inRollout(flag: string, subjectId: string, percent: number): boolean {
  const digest = createHash("sha256").update(`${flag}:${subjectId}`).digest();
  const bucket = digest.readUInt32BE(0) / 2 ** 32; // uniform in [0, 1)
  return bucket < percent / 100;
}

// Raising the percentage only adds subjects; nobody already included is removed.
const enabled = inRollout("bulk_import", workspace.id, 50);

Including the flag name in the hash matters. Without it, the same workspaces would fall into the first 10% of every rollout and become the permanent test group for everything. Bucket by the unit you analyze, too: if the metric is per workspace, bucket by workspace, not by user, or members of one team will see different products.

05 / 06

Keep the code as changeable as the plan

The engineering habits that support this are not exotic. Keep domain logic separate from delivery details, so the import rules can be tested without a browser and reused when the API needs them. Make interfaces explicit where there is a real boundary, and resist layers for requirements that have not arrived. Write the tests that let you change code with confidence, starting with the logic most likely to change.

Then clean up. Give every release flag a removal date when it is created. A flag that has sat at 100% for a month is dead code with a configuration switch attached, and every one left in place doubles the paths someone has to reason about. Deleting code is part of finishing a release, not a separate chore.

06 / 06

Before the next release

  • Is there a written question, a leading metric, guardrails, and decision rules?
  • Is the first slice thin enough to ship in days and still answer the question?
  • Is it behind a flag with a kill switch, with assignment stable per unit of analysis?
  • Can the team see the metrics per cohort without filing a data request?
  • Does the flag have a removal date?

Five yes answers do not guarantee a good feature. They guarantee that the team will find out quickly, and cheaply, whether it has one. That is the way we run custom software development engagements.

References

  1. Feature Toggles (aka Feature Flags) Pete Hodgson, martinfowler.com

    Toggle categories, lifetimes, and implementation techniques.

  2. Opportunity Solution Trees Teresa Torres, Product Talk

    Connecting outcomes, opportunities, and the experiments that test them.

  3. Trunk-based development DORA

    Research on small batches and short-lived branches as a delivery capability.

  4. Shape Up Ryan Singer, Basecamp

    Fixed time, variable scope: shaping and slicing product work.

HOW NEURALSHARKS CAN HELP

Related NeuralSharks services