↓ Skip to main content

Rethinking Code Review in the Age of LLMs

Code review is a bottleneck. I am no longer convinced it is a useful one.

This is not a conclusion I arrived at lightly. Code review has been one of the most reliable quality gates in software engineering for decades. But the assumption underlying code review, that a human reading code before it ships catches meaningful problems, is worth re-examining when most of that code was written by an LLM.

What Code Review Was Supposed to Do
#

Code review served several purposes simultaneously.

It caught bugs before they reached production. It enforced consistency across a codebase. It spread knowledge between team members. It forced the author to organize their thoughts before presenting them to a peer.

Each of these purposes assumed something important: that the person who wrote the code and the person reviewing it were both human, that the author had thought carefully about each line, and that the reviewer could rely on the author’s intent as context.

When an LLM writes the code, these assumptions break.

The LLM did not think carefully about each line. The LLM does not have intent in the way a human does. The reviewer cannot ask the author “what were you trying to do here?” and get a meaningful answer, because the author is a statistical model that generated the most probable next token.

The loss of a human author changes the nature of the review fundamentally, and not in the direction most people assume.

Why Reviewing LLM Code Is Different
#

When a human writes code, code review is a conversation between two people who share a mental model. The reviewer can trust that the author made deliberate choices, even imperfect ones. Differences between what the reviewer expects and what the code does are informative, because they represent a difference between two human understandings of the same problem.

When an LLM writes code, there is no shared mental model. The code is the output of a pattern-matching process. Sometimes it is correct. Sometimes it is subtly wrong in ways that look correct. Sometimes it is obviously wrong.

The reviewer’s job shifts from “does this match the author’s intent?” to “does this do what I want?” This sounds like the same question, but it is not. The first question allows the reviewer to leverage the author’s reasoning. The second question requires the reviewer to independently verify every assumption the code makes.

This is harder, more tedious, and less effective than reviewing human-written code. The reviewer cannot use the author’s thinking. They are reconstructing it from the beginning, line by line.

The Bottleneck Argument
#

Here is the practical problem.

LLMs can generate code orders of magnitude faster than humans can review it. A developer who used to spend six hours implementing a feature might now spend thirty minutes prompting an LLM and one hour reviewing the output.

The ratio of review time to implementation time has inverted. Where review used to be a small fraction of the development cycle, it is now the dominant fraction.

And the quality of that review is worse, not better, because reviewing code you did not write is cognitively different from reviewing code you understand deeply.

This creates a specific kind of bottleneck: one where the throughput-limiting step is also the lowest-quality step. You are spending most of your time on the part of the process where you add the least value.

What I Would Rather Be Doing
#

If I am going to spend my limited cognitive budget, I want to spend it on the decisions that matter most.

Deciding which problems to solve. Understanding whether a feature should exist at all. Designing the boundary between components. Choosing the right abstraction for the domain. Thinking about how users will actually interact with what we build.

These are high-leverage activities. They determine whether the code that gets written is useful, not just correct.

Reviewing LLM-generated code for style, naming conventions, and obvious bugs is low-leverage work. An automated tool can do it faster and more consistently than I can. A linter does not get tired. An automated test suite does not lose focus after the third function.

The hours I spend reviewing code that an LLM wrote are hours I am not spending on the problems that only a human can solve. That trade-off did not used to exist, because writing code and reviewing code were both human activities and the time allocation was roughly balanced. Now the balance has shifted, and the opportunity cost of review is much higher.

Why Automatic Review and Approval Make Sense
#

I have no objection to automated code review. I have no objection to automated PR approval.

The instinct to involve a human in every change is driven by fear, not by a rational assessment of what the human actually contributes at that point in the process.

Consider what a good code reviewer does today. They check that tests pass. They look for obvious bugs. They verify naming conventions. They ensure the change aligns with the stated goal.

Every one of these checks can be automated. Tests pass or they do not. Static analysis tools catch bugs more reliably than tired humans scanning diffs. Linters enforce style more consistently than any reviewer. Alignment with the stated goal can be checked by having an LLM compare the PR description with the actual changes.

The Wrong Stage to Catch Subtle Issues
#

The remaining argument for human review is that humans catch subtle issues that automated tools miss: architectural problems, subtle security vulnerabilities, misunderstandings of the domain.

These are legitimate concerns, but they are not best addressed at the PR level. They are best addressed at the specification level. If you write a precise specification, automated verification can confirm the implementation matches it. If the specification is vague, no amount of human review will save you from building the wrong thing.

Some would argue that catching these issues during review is better than not catching them at all. This is hard to disagree with in isolation. Of course a subtle bug caught at review is better than the same bug reaching production.

But the better-late-than-never framing conceals the actual trade-off. The question is not whether review catches some problems. It is whether review is the best place to catch them, and whether the time spent reviewing could catch more problems if spent elsewhere.

When you catch an architectural flaw at review time, the code is already written. Fixing it means rework, rebase, re-test, re-review. Catching the same flaw during specification costs a conversation. The later you catch it, the more expensive it is, and code review is one of the latest stages in the pipeline.

More importantly, relying on review to catch subtle issues is unreliable by design. A human reviewer catches what they happen to notice, when they happen to be alert, on the changes they happen to read carefully. Some issues get caught. Many do not. You are depending on luck and attention, not on a system.

The alternative is to build the catching into the system itself. A precise specification catches architectural misunderstandings before code exists. A comprehensive test suite catches behavioral bugs on every run, not just when a reviewer is paying attention. Static analysis catches security patterns deterministically. Each of these catches issues systematically, on every change, forever.

Review catches issues once, for the reviewer who happens to be in front of the diff. A good test catches the same class of issue every time it runs, for as long as the codebase exists. If you find the same kind of subtle issue during review more than once, the answer is not to keep reviewing harder. The answer is to encode that check into your automated gates so it never depends on a human noticing again.

“Catching it late is better than not catching it” is true. It is also an argument for accepting a process that catches too little, too late, at the highest possible cost.

The review should happen before the code is written, not after. Spend the human effort on the spec. Let the machines verify compliance.

The Quality Maximization Myth
#

Some would argue that this misses the point. The goal of code review is not just to check gates, it is to make the code the best it can be. A good reviewer suggests a cleaner abstraction, spots a performance issue the author missed, proposes a name that communicates intent better. Review is quality maximization, not just verification.

This sounds right until you examine what “the best it can be” actually means.

It is subjective. It is unbounded. There is always a cleaner abstraction, a faster algorithm, a better name. The pursuit has no natural stopping point, which is why code reviews so often devolve into bikeshedding over style preferences that do not measurably improve the outcome.

When the code was written by a human, a second perspective genuinely improved the implementation. Two people could find a better approach than one person. But when the code is generated by an LLM, the reviewer is the only human involved, and their suggestions compete with the option of simply regenerating the code against a better specification.

If the code is not good enough, the answer is not to have a human improve it line by line during review. The answer is to improve the specification, the test suite, or the generation prompt, and let the machine produce a better version. That scales. Human suggestions on a diff do not.

The quality of the code is bounded by the quality of the specification that produced it. If you want better code, write a better spec. Reviewing the output is the most expensive, least scalable way to improve it.

The Reallocation
#

Here is what I am proposing.

Instead of spending 30% of development time on code review, spend 5% on automated verification and reallocate the saved hours to specification, problem selection, and domain understanding.

The quality of the software will not decrease. The tests still run. The linters still lint. The static analysis still analyzes. What changes is where the human attention goes.

Right now, human attention is concentrated at the end of the pipeline, reviewing output. It should be concentrated at the beginning of the pipeline, defining what the output should be.

This is not a radical idea. It is the same principle behind test-driven development: define what you want first, then build it. The difference is that now the builder is a machine, and the definition is the only place where human judgment is irreplaceable.

This Is Not Hypothetical
#

None of this is a prediction about the future. I run this approach today.

My development workflow front-loads human effort into specification, requirements, and problem selection. Before any code is written, there is an issue with acceptance criteria, a requirements document, and a technical specification, each reviewed and approved. That is where my judgment gets spent.

Code generation happens against that specification. When a pull request is opened, an automated review pipeline checks it against concrete gates. Does it introduce security-sensitive changes? Does it add new dependencies? Does it change public interfaces? Is the change reversible? Do the tests pass? Does the diff actually satisfy the acceptance criteria from the linked issue?

If all gates pass, the PR is approved automatically. No human reads the diff. No human clicks approve.

When the gates flag something, the PR is held for manual review. This is not the same as reviewing every change. It is reviewing the changes that carry genuine risk, which is a much smaller set.

The distinction matters. I am not advocating for shipping unreviewed code. I am advocating for shipping code that has been verified by systems more reliable than a tired human scanning a diff, and reserving human attention for the small fraction of changes where it actually adds value.

The infrastructure for this exists. The gates are concrete. The approach works because the specification does the work that code review used to do poorly. The skills that implement this workflow are publicly available.

Who Is Responsible
#

The first objection is accountability. If no human reads the code before it ships, who owns the problems it creates?

The answer is that a human reviewing a pull request for ten minutes was never truly responsible for that code. They provided a rubber stamp. They glanced at the diff, checked that the tests passed, and clicked approve. When that code caused an outage six months later, nobody blamed the reviewer. They blamed the author, the test suite, the deployment process, or the requirements.

What about the reviewer who spends thirty minutes, or an hour? They are doing substantive work, not rubber-stamping. The characterization above does not apply to them. They genuinely understand the change, question the design, and catch actual issues.

But the argument against mandatory review does not depend on reviews being shallow. It depends on where that hour of expert attention is best spent.

An hour of review catches issues once, for one change, depending on that reviewer being alert that day. An hour spent improving the specification prevents the entire class of issue from reaching implementation. An hour spent writing a regression test catches the bug on every future run, not just the one time a human happened to read the code.

The thorough review is genuine work. It is just not the highest-leverage work that person could be doing with that hour. And at scale, it is unsustainable: if every PR requires an hour of human review and the LLM produces ten PRs a day, you need ten hours of review to keep up. That is not a process that scales.

Code review creates an illusion of accountability without delivering it. The signature on the PR is a formality.

Responsibility belongs upstream. The person who decided this problem was worth solving owns the outcome. The person who wrote the specification owns whether the implementation matches intent. The person who designed the deployment pipeline owns how quickly a bad change can be contained.

Removing human review does not remove responsibility. It forces you to locate responsibility where it actually belongs: in the decisions that guided the work, not in the person who scanned it at the end.

What About Outages
#

The second objection is safety. What if the LLM makes a decision that takes down production?

This is a genuine risk, but code review is the wrong tool to mitigate it.

Most production outages are not caused by bugs that a reviewer would catch. They are caused by configuration changes, unexpected data formats, load patterns, dependency failures, and integration issues that only surface under actual traffic. A human reading a diff is making a guess about what might happen. Production behavior is the ground truth.

If you want to prevent outages, invest in the systems that observe and contain actual behavior.

Feature flags so a change can be turned off without a redeploy. Canary deployments so a bad change reaches 1% of traffic before it reaches 100%. Monitoring and alerting so a regression is detected in minutes, not hours. Fast, tested rollback paths so recovery does not depend on someone remembering how the old version worked.

These tools are more reliable than code review because they operate on reality rather than prediction. A reviewer might miss a subtle interaction. A canary deployment will surface it.

What About Reversibility
#

A related concern is that an LLM might produce changes that are hard to undo. A sprawling refactor, a database migration that is not backward compatible, a change that entangles two previously independent systems.

This is a serious problem, but again, human review is not the safeguard people think it is.

Reviewers focus on forward correctness. They ask “does this do what it should?” They rarely ask “can we undo this cleanly?” Even when they do, reversibility is hard to assess by reading a diff. It depends on what happens in production after the change is deployed, on data migrations that have already run, on other changes that depend on it.

The solution is to make reversibility a structural property of how changes are made, not a property enforced by review.

Keep changes small and independent. Require database migrations to be backward compatible, deployed before the code that depends on them. Use feature flags so new behavior can be disabled without reverting code. Treat large, entangling refactors as high-risk changes that warrant extra process, not as the default mode of operation.

These are constraints you encode in your pipeline and your specification process. They do not require a human to read every line of every PR. They require discipline at the level where decisions are made.

What About Code Quality and Maintainability
#

A third concern is that without human review, LLM-generated code will degrade into slop. Inconsistent naming, duplicated logic, unnecessary abstractions, dead code, patterns that do not match the rest of the codebase. Each individual change passes its tests, but the codebase slowly degrades.

This is a genuine risk, and it deserves a serious answer. The answer is not that review prevents it. The answer is that review catches it inconsistently, after the fact, one PR at a time.

Most of what we call code quality is measurable. Cyclomatic complexity is a number. Duplication is detectable. Dead code is identifiable by static analysis. Naming conventions are enforceable by linters. File length, function length, import depth, test coverage gaps, all of these are machine-checkable properties.

If you care about maintainability, encode the constraints that produce it. Set complexity limits that fail the build. Run duplication detectors on every PR. Require test coverage above a threshold. Lint aggressively. Block PRs that introduce unused exports or dead code.

These checks run on every change, consistently, without getting tired or distracted. A human reviewer might flag a function that is too complex. A complexity gate will flag every function that exceeds the threshold, every time, for as long as the rule exists.

Slop also has an upstream cause. When the specification is vague about architecture, naming, and patterns, the LLM fills in the missing details with whatever it has seen most often in its training data. That output is generic by default. It will not match your codebase’s conventions unless the specification tells it what those conventions are.

This means slop is prevented at the specification level, not at the review level. A specification that includes the patterns to follow, the existing abstractions to reuse, and the naming conventions to respect produces cleaner code than a vague spec plus a human reviewer cleaning up the output.

For whatever the automated gates miss, frequent maintenance sweeps catch accumulated decay. Run a dead code analysis weekly. Run a duplication detector weekly. Review complexity trends after every merge. This is a more systematic approach than hoping each PR’s reviewer notices the slow accumulation of mess.

Code quality is a property of your constraints, not of your reviewers.

What I Am Not Saying
#

I am not saying all code review should be eliminated tomorrow. Legacy codebases, critical security infrastructure, and domains where correctness is life-or-death may still benefit from human review.

I am not saying code review was never useful. It was, for decades, one of the best tools we had. I am saying the tool’s value has changed because the context has changed.

I am not saying I trust LLMs to always produce correct code. I am saying that human code review is not the best way to ensure correctness when the code was machine-generated. Automated verification, comprehensive test suites, and precise specifications are better tools for that job.

The Question Worth Asking
#

The next time you open a pull request full of LLM-generated code and start reviewing it line by line, ask yourself: what am I actually checking?

If you are checking style, a linter does it better. If you are checking correctness, tests do it better. If you are checking whether the code solves the right problem, you should have answered that question before the code was written.

Code review made sense when humans wrote all the code. It makes less sense when humans define the problem and machines implement the solution. The bottleneck has moved. Our processes should move with it.


Scaling the LLM Agent Company

In Exponential growth software company I explored the constraints that make scaling a human company hard: onboarding bottlenecks, culture dilution, coordination overhead, institutional knowledge decay. Each of these constraints grows with the number of people you employ. A company where the workforce is entirely composed of LLM agents inverts most of these problems. The old constraints disappear, but new ones replace them.

What Disappears
#

Onboarding becomes instantiation. An LLM agent starts productive the moment it is created. There is no training period, no ramp-up, no senior employee pausing their work to bring a new hire up to speed. Spawning 100 agents costs roughly the same operational effort as spawning 1. The ratio of onboarded to onboarders that mattered for human companies becomes irrelevant.

Culture is exactly what you specify. Agents follow their instructions. If you want 1,000 agents to behave identically, you give them the same system prompt. There is no culture clash, no informal norms developing in opposition to the official ones, no gradual drift as new hires bring outside habits. The culture is the prompt.

Coordination scales differently. Agents do not have cognitive limits on the number of peers they interact with. A single orchestration agent can coordinate with hundreds of worker agents without getting overwhelmed. Communication between agents is structured, lossless, and near-instantaneous. The team structures, single points of contact, and redundancy tradeoffs that human organizations require are replaced by protocol design.

Institutional knowledge does not decay. Everything an agent knows is explicit in its instructions, tools, and retrieved context. There is no tacit knowledge that exists only in someone’s memory, no risk of losing the person who understands the legacy system. When you replace an agent, the replacement has access to exactly the same information.

What Replaces It
#

The fundamental principle from the original article still holds: anything that scales linearly or superlinearly with itself needs to be optimized to grow sublinearly. The bottleneck has moved, but it has not disappeared.

Unit economics
#

Each agent invocation costs compute and API calls. At scale, the marginal cost of an additional agent is small but not zero. If your revenue per task is lower than the cost of the compute required to complete it, you have the same fundamental problem as an unprofitable human company. A human company that loses money on every employee-hour goes bankrupt. An agent company that loses money on every inference call goes bankrupt just as fast, it just happens in milliseconds instead of months.

Correlated failures
#

With humans, errors are diverse and partially cancel out through independent judgment. Two engineers given the same task will produce different bugs. With agents sharing the same prompt, errors are correlated and systemic. A single flawed instruction propagated across 1,000 agents produces 1,000 instances of the same mistake at scale. Consistently wrong is worse than inconsistently right. Correlated errors make quality control the central bottleneck. You need evaluation pipelines, guardrails, and feedback loops that themselves must scale. The testing infrastructure becomes the company’s most critical asset, more important than the agents it tests.

Orchestration complexity
#

While individual agents do not get overwhelmed, the system as a whole can still produce emergent failures. Agents acting on stale information, conflicting instructions, or subtle misinterpretations of their goals can cascade into problems that are hard to diagnose because the system operates at a speed and scale humans cannot directly oversee. The orchestration layer is the new management layer. Its complexity grows with the number of agents and the richness of their interactions. Unlike human management, where adding managers adds judgment and adaptability, adding orchestration logic adds rigidity. Every new rule is a new potential point of failure.

Infrastructure brittleness
#

A human can work around a broken tool with creativity. An agent generally cannot. If an API goes down, every agent depending on it stalls. If a database schema changes unexpectedly, every agent writing to it produces corrupted data. The brittleness of automated systems means that reliability engineering and observability become more important, not less, as you scale. The company that cannot detect a degrading agent within seconds will compound the damage across its entire workforce simultaneously.

Model dependency
#

Your company’s capacity is bounded by what the underlying models can do. If the model provider changes behavior, degrades performance, or raises prices, your entire workforce is affected at the same time. This is a vendor dependency unlike any single human employee leaving. It is more like all your employees depending on the same external model that a third party operates. Diversifying across models is a partial hedge, but it introduces the same coordination complexity as a multilingual workforce.

Where the Moat Lives
#

When anyone can spawn an equally capable agent workforce, the advantage is no longer in having employees. The moat shifts to the quality of your instructions (prompts as institutional knowledge), the design of your orchestration (workflows as management), your data flywheels (evaluation data as competitive advantage), and domain-specific tools and integrations (proprietary capabilities the agents use). The company is no longer its people. It is its prompts, its pipelines, and its proprietary context.

The Pattern
#

The original article concluded that human scaling requires optimizing every linear cost down to sublinear. The same conclusion applies here, but the costs are different. Human companies optimize hiring, onboarding, and culture. Agent companies optimize inference cost, error correlation, orchestration complexity, and infrastructure reliability. The companies that scale exponentially in this era will be the ones that treat their agent workforce as a system to be engineered, not a team to be managed.

References
#


ghx - A CLI for agentic code reviews on GitHub

I’ve been building AI agents that review pull requests, and the official gh CLI doesn’t have what they need. Agents can’t leave inline comments on specific lines, can’t manage pending reviews, and can’t edit or delete comments. These are basic operations for any code review workflow, and they’re only accessible through the GitHub web UI or the raw GraphQL API.

So I built ghx, a CLI designed to make agentic code reviews practical.

What ghx does that gh doesn’t
#

Inline comments on files and lines. The most fundamental operation for a code review agent: comment on a specific line of a diff.

$ ghx pr comment 42 --file src/main.go --line 10 --body "Nit: use fmt.Errorf"
Created inline comment on src/main.go:10 (thread PRRT_kwDOC0I7As5vKgVn)

$ ghx pr comment 42 --file src/main.go --line 10-15 --body "Consider extracting this"
Created inline comment on src/main.go:10-15 (thread PRRT_kwDOC0I7As5vKgVq)

File-level comments (without --line), top-level PR comments, and replies to existing threads are all supported.

Pending reviews. Accumulate review comments without submitting them immediately, then approve or comment when ready:

$ ghx pr comment 42 --file src/main.go --line 10 --body "Nit" --pending
Added pending inline comment on src/main.go:10 (thread PRRT_kwDOC0I7As5vKgVz, review PRR_kwDOC0I7As4B9Y2z)

$ ghx pr review submit 42 --event APPROVE --body "LGTM"
Submitted review PRR_kwDOC0I7As4B9Y2z as APPROVE

Edit and delete comments. Fix a typo or remove a comment. Use ghx pr threads 42 --ids to list IDs:

$ ghx pr threads 42 --ids
PRRT_kwDOC0I7As5vKgVn  src/main.go:10  [open]
  PRC_kwDOC0I7As5TKxYc  reviewer  Nit: use fmt.Errorf

  PRC_kwDOC0I7As5TKxYd  author  Good catch, will fix.

Then edit or delete the comment:

$ ghx pr comment edit PRC_kwDOC0I7As5TKxYc --body "Use fmt.Errorf instead of errors.New"
Updated comment PRC_kwDOC0I7As5TKxYc

$ ghx pr comment delete PRC_kwDOC0I7As5TKxYc
Deleted comment PRC_kwDOC0I7As5TKxYc

Review thread management. List, filter, and inspect review threads:

$ ghx pr threads 42
src/main.go:10  [open]
  reviewer  Nit: use fmt.Errorf
  author    Good catch, will fix.

src/main.go:45-52  [resolved]
  reviewer  Consider extracting this into a helper
  author    Done in 3a1b2c4

Filter by state with --state open, --state resolved, or --state all.

Issue comments and viewing. Add, edit, and delete issue comments, and view issues with their full comment history:

$ ghx issue view 42
Fix race condition in worker pool  [open]  author

The worker pool has a race condition when multiple goroutines access
the shared counter without proper synchronization.

2 comment(s):

  contributor  I can reproduce this with `go test -race ./...`
  author       Fixed in #50

$ ghx issue comment 42 --body "This is fixed in #50"
Created comment IC_kwDOC0I7As5TKxZa on issue #42

The stash system
#

ghx has a local stash system for review comments, modeled after git stash.

The main use case is enabling agents to batch many comments at once before submitting them all as a single review. Instead of making individual API calls per comment, an agent can stash comments locally and pop them into a pending review in one operation:

$ ghx pr comment 42 --file src/main.go --line 10 --body "Nit" --stash
Stashed comment on src/main.go:10 (stash@{0} now has 1 threads)

$ ghx pr comment 42 --file src/main.go --line 20-25 --body "Extract this" --stash
Stashed comment on src/main.go:20-25 (stash@{0} now has 2 threads)

$ ghx pr review stash list 42
stash@{0}:  2 threads, 2 comments
  src/main.go   10      1 comment(s)
  src/main.go   20-25   1 comment(s)

$ ghx pr review stash pop 42
Popped stash@{0} (2 threads, 2 comments) into review PRR_kwDOC0I7As4B9Y2z

The stash also solves a GitHub API constraint: you can’t mix immediate comments with pending review comments on the same PR. When you submit an immediate comment on a PR that has a pending review, ghx automatically stashes the pending review, submits the comment, and restores the pending review. The stash lets agents use a push-pop workflow (stash, comment, comment, pop) instead of repeating push-comment-pop for every immediate comment.

The stash supports multiple entries, just like git stash: push, pop, drop, and list.

Getting started
#

ghx is a single Go binary. Download the latest release from GitHub, or install with Go:

go install github.com/tomzxcode/ghx@main

ghx picks up your existing GH_TOKEN, GITHUB_TOKEN, or gh auth login credentials. No extra configuration needed. All commands accept --repo OWNER/REPO or auto-detect from the current git remote.

ghx is MIT-licensed and written in Go with no runtime dependencies beyond the GitHub API.


The Backlog Is Not a Dumping Ground: Managing the Backlog of a Large Software Product

A well-managed backlog is the single most useful artifact in product development. When it works, teams ship the right things at a sustainable pace. When it doesn’t, the organization accumulates stale tickets, conflicting priorities, and endless planning meetings. For large software products, where dozens of teams feed from the same corpus of work, the cost of poor backlog hygiene is enormous and mostly invisible.

The Backlog at Scale Behaves Differently
#

On a small team, the backlog is a conversation. The product owner and a handful of engineers look at a list, discuss what matters most, and pick up work. At scale, the backlog becomes an information system. It mediates between strategy and execution, between stakeholders who want things and teams who build things, and between the present state of the product and its future state.

The Scrum Guide defines the product backlog as “an emergent, ordered list of what is needed to improve the product” and notes that it is the single source of work for the team. That definition is clean and useful, but in practice, large organizations layer in program backlogs, initiative backlogs, discovery backlogs, and technical debt backlogs. The first challenge is deciding what structure you need and resisting the urge to create a new list every time someone feels their priorities aren’t getting enough visibility.

Marty Cagan, “Inspired” argues that the strongest product teams maintain a clear distinction between product discovery (figuring out what to build) and product delivery (building it), and that conflating the two is a root cause of bloated backlogs. Items enter the backlog before anyone has validated that they are worth building. The backlog becomes a list of desires rather than a commitment queue.

Four Principles That Change Everything
#

Most backlog problems come from violating one of four principles.

1. The Backlog Is Ordered, Not Merely Prioritized
#

“Prioritized” suggests labels like “high,” “medium,” and “low.” Those labels are nearly useless because everything ends up “high.” Ordered means the backlog is a ranked list: item 1 is more important than item 2, which is more important than item 3. A strict ordering is harder to produce, and that difficulty is deliberate. Forcing a strict ordering surfaces the trade-offs that stakeholders would otherwise avoid.

Woody Zuill’s approach of “pulling” work rather than “pushing” it is relevant here. When the backlog is strictly ordered, teams pull from the top. No selecting favored items, no lobbying for a personal favorite feature. The rank order is the contract.

At scale, this principle applies at every level. Initiatives within a program are ordered. Epics within an initiative are ordered. Stories within a sprint are ordered. If you cannot say which of two items matters more, you are not ready to put both in the backlog.

2. Only Refined Items Belong in the Delivery Backlog
#

Jeff Patton’s user story mapping technique provides a framework for separating discovery from delivery. The “backbone” of user activities and the stories beneath them form a map of the product’s possible future. Only stories that have been discussed, estimated, and accepted by the team belong in the sprint-level delivery backlog. Everything else lives in a discovery or discovery-adjacent artifact.

Separating discovery from delivery is a critical structural choice. Many organizations dump every idea, bug report, customer request, and technical improvement into the same GitHub project. Within months, that project contains thousands of items in no particular order. The team treats it as a write-only data structure.

A practical pattern: maintain two lists. The product backlog contains everything the team might ever work on, loosely grouped by theme. The sprint backlog (or “ready” queue) contains only items that meet a definition of ready: they have acceptance criteria, they are small enough to complete in a single sprint, and the team has discussed them. Nothing moves from the product backlog to the sprint backlog without passing through a refinement session.

The Scrum Guide calls this ongoing activity “backlog refinement,” and regular sessions where the team reviews the top portion of the backlog, splits large items, and ensures alignment with the product goal are essential. The key discipline is not the meeting itself but the agreement that unrefined items will not be scheduled.

3. Prioritization Frameworks Are a Means, Not an End
#

Several frameworks exist to help teams rank backlog items:

Framework Mechanism Best suited for
RICE (Reach, Impact, Confidence, Effort) Numerical score combining reach, impact, confidence, and effort Product teams comparing features across different domains
WSJF (Weighted Shortest Job First) Cost of delay divided by job size Organizations using SAFe or managing program-level backlogs
Opportunity Scoring Measures importance and satisfaction for each outcome Discovery-phase prioritization when outcomes are unclear
ICE (Impact, Confidence, Ease) Lightweight scoring for rapid triage Fast-moving teams that need quick decisions

These frameworks are useful because they make the prioritization criteria explicit. They are dangerous when teams treat the resulting score as the final answer. A RICE score is only as good as the estimates that feed it, and those estimates are often wrong.

The right approach is to pick one framework that matches your organization’s needs, use it consistently, and revisit the scoring regularly. The conversation that happens when two stakeholders disagree about a score is where the value lives. The number itself is a forcing function, not a decision.

4. Say No Early and Often
#

The most important word in backlog management is “no.” Every item in the backlog carries a cognitive tax. Engineers scroll past it in planning. Product managers feel obligated to explain why it hasn’t been done. Stakeholders check on it periodically. The larger the backlog, the more time the team spends managing the backlog instead of working from it.

Derek Sivers’ “hell yeah or no” heuristic applies here. If an item does not clearly advance the product’s current goals, decline it, archive it, or move it to a separate “someday” list that nobody is expected to maintain. A product backlog with 30 well-ordered items is more useful than one with 300 loosely grouped items.

At scale, this principle requires organizational courage. Every stakeholder believes their request is important. The product manager’s job is to say no to the things that are less important than the current top priority, even when that is uncomfortable. Cagan frames this as the difference between product teams (who are empowered to solve problems) and feature teams (who are handed a list to implement). Empowered teams can say no because they understand the problem they are solving and can judge whether a given request advances the product’s goals.

Structural Patterns for Large Products
#

When a product involves multiple teams, shared infrastructure, and cross-cutting concerns, the backlog structure needs to accommodate complexity without collapsing under it.

The Tiered Backlog
#

Large products benefit from a tiered backlog structure:

  1. Strategic tier: Outcomes, OKRs, or product goals for the quarter. Owned by leadership and product management. Updated quarterly.
  2. Initiative tier: Named efforts that advance a strategic outcome. Owned by product managers. Updated monthly.
  3. Delivery tier: Epics and stories that decompose an initiative. Owned by individual teams. Updated continuously.

Each tier feeds the one below it. The strategic tier determines which initiatives enter the initiative tier. The initiative tier determines which epics and stories appear in the delivery tier. Information flows up through demos, retrospectives, and metrics.

The critical rule: no item in a lower tier can exist without a parent in the tier above it. Orphaned work is the primary source of backlog bloat. If someone wants to add a story that doesn’t map to an active initiative, the answer is either to create an initiative (which triggers the prioritization process) or to decline the story.

The Obeya Room Pattern
#

Borrowed from lean manufacturing and popularized by Toyota, the Obeya (“big room”) is a physical or virtual space where cross-functional representatives meet regularly to review the state of the product. The backlog is visualized on the wall (or in a shared tool), and the group discusses priorities, blockers, and dependencies as they happen.

For large products, the Obeya pattern addresses a problem that no tool solves: alignment. When eight teams are pulling from the same backlog, local optimization is the default. Each team optimizes for its own velocity and its own priorities. The Obeya creates a forum where global optimization can happen.

The Regular Grooming Rhythm
#

A weekly or biweekly refinement session where the product manager and the team review the top 10-15 items in the backlog is a widely recommended practice. The goal is not to estimate everything but to ensure that the next two to three sprints’ worth of work is well understood.

For large products, this rhythm needs to exist at every tier. Leadership reviews the strategic tier quarterly. Product managers review the initiative tier monthly. Teams refine the delivery tier weekly. The cadence prevents the backlog from becoming stale and ensures that the most important items are always the most visible.

Common Failure Modes
#

The infinite backlog. A backlog that never shrinks is not a backlog; it is a suggestion box. If the backlog has more than a few hundred items, most of them are irrelevant. Archive aggressively.

The stakeholder lobby. In large organizations, stakeholders learn to mark everything as “critical” or to escalate items directly to engineering managers. The ordered backlog is the defense against the stakeholder lobby. If the item is not in the top of the ranked list, it does not get worked on, regardless of who asked for it.

The technical debt blind spot. Product backlogs tend to favor features because features have visible stakeholders. Technical debt has no natural advocate. The solution is to allocate a fixed percentage of capacity (often 20-30%) to technical improvement and to make that allocation explicit in the backlog.

Pointless early estimation. Spending hours estimating items that are months away from being worked on is waste. Estimate just enough to support prioritization, and re-estimate when the item moves into the delivery backlog. Ron Jeffries, “Story Points Revisited” argues, as one of the originators of story points, that estimation should support planning, not become a goal in itself.

Conflating bugs and features. A bug is a commitment to fix something that was promised. A feature is a new investment. Mixing bugs and features in the same backlog without distinguishing between them leads to either under-investment in new value (because bugs always seem urgent) or neglect of quality (because features always seem more strategic). Separate them, fund them differently, and track them separately.

What to Do Next
#

If your backlog is currently a mess, here is a sequence that works:

  1. Archive everything older than six months that has not been touched. If it mattered, someone would have advocated for it. You can always pull items out of the archive.
  2. Identify the current product goal. If you cannot state it in one sentence, you are not ready to prioritize the backlog.
  3. Map every remaining item to the goal. Items that don’t map go to a separate “someday” list.
  4. Force-rank the top 20 items. No ties. This exercise will reveal every disagreement about priorities, which is exactly what you need.
  5. Refine the top 5-10 items until they meet your definition of ready. Only these items are eligible for the next sprint.
  6. Set a weekly refinement cadence and protect it. The backlog becomes stale without maintenance.

A healthy backlog is small, ordered, and refined. It reflects a clear product goal and a shared understanding of what matters most. Maintaining the backlog is not glamorous work, but it is the work that makes everything else possible.


What I've built and what I need: May 2026

The past month has been about turning repetitive workflows into reusable skills, and the gaps that remain are mostly about making those skills smarter, not more numerous.

What I Have Been Working On
#

Built a full SDLC skill pipeline. I shipped a comprehensive software development lifecycle orchestrator in agents that chains over 20 sub-skills, from issue creation through learnings capture. It handles phase contracts, backtracking when upstream artifacts are incomplete, and fast paths for small work like bug fixes and config changes. The pipeline tracks artifact status via YAML frontmatter and stores everything under .sdlc/ with a consistent directory structure.

Made issue tracking actually useful. I have been using create-issue heavily over the past month to track gaps in the software I am building. It is not perfect, but it is better than not tracking the work, and it captures more context than I would take the time to write by hand.

Automated PR descriptions. I use create-pr-description to generate PR descriptions based on code changes and intent. Writing those manually was slow and inconsistent; now the descriptions reflect what changed without the manual effort.

What I Currently Need
#

Test the SDLC pipeline on real work. The SDLC skill is built but has not been stress-tested end to end on real feature work. I need to run it through enough varied scenarios to surface the gaps between the design and practical use.

Scheduled issue-to-PR automation in openchamber. Openchamber can already create a worktree per directory and execute a prompt, but it does not run on a regular schedule. I need it to pick up new issues, execute the full pipeline, and open PRs without manual triggering.

Automated issue triaging in open source projects. I built a triage-issues skill but have not used it on my own repositories. The goal is to reduce the burden of going through issues to identify duplicates and decide whether they should be acted on.

Memory that agents manage automatically. Right now memory requires explicit reads and writes. I need agents to store, retrieve, and decay knowledge across sessions without me prompting them to do it.

Contextual Slack support. I need a way for users asking for help on Slack to receive contextually relevant information, and for the system to learn from human-to-human interactions and the answers people give each other. I started building slack-cached to cache and query Slack conversations.

Automatic context clearing between execution and review. Running implementation and review in the same context introduces bias. I need a mechanism, likely using forked subagents, to /clear between execution and review automatically so the reviewer starts fresh.

Accuracy pass on daily summaries. I have accumulated daily summaries that contain inaccuracies. I need something to go through them and correct what is wrong, rather than letting bad data compound over time.

Incremental PR description updates. When I update a PR after the description is written, I need create-pr-description to adjust minimally, appending or amending what changed, rather than regenerating the whole thing from scratch.

Skill usage tracking. I need to know how often each skill is invoked and when. Without that data, I cannot tell which skills are worth keeping and which I never use.

Replying to inline PR comments. gh does not support replying to inline review comments programmatically. Tools like gh-pr-review exist but feel awkward for what should be a straightforward operation. I need a clean way to post inline replies as part of the review and feedback skills. I started building ghx to address this gap.


Software Engineering Teams in the Age of AI: Smaller, Sharper, Intentionally Imperfect

LLM-powered coding assistants have changed what an individual engineer can produce in a day. The harder question is what this means for how teams should be organized, how they should work together, and which of their existing processes are still worth keeping. My answer is counterintuitive: the teams that thrive will not be the ones that adopt AI fastest or eliminate the most process. They will be the ones that correctly distinguish between friction that wastes time and friction that prevents mistakes.

What Actually Changed
#

AI-assisted development compresses the time from idea to working code. An engineer with a capable LLM can prototype a feature in hours that used to take days. Boilerplate, tests, documentation scaffolding, and CRUD endpoints all cost less and less time.

But the things that remain expensive have not changed at all. Deciding whether to build a feature, choosing the right abstraction, understanding the domain deeply, and aligning technical work with business goals are as hard as they ever were. In many ways the expensive parts are harder now, because the temptation to just generate and ship without deciding is stronger.

This creates a specific tension for teams. When individuals can produce more code, the bottleneck shifts from production to coordination and judgment. Team design has to account for this shift, not ignore it.

Team Size: Follow the Problem Boundary
#

There is a persistent urge to declare an ideal team size. Amazon popularized the “two-pizza team” rule. Agile methodology settled on 3 to 9. Various management frameworks have their own magic numbers.

AI does not provide a new magic number, but it does shift the trade-off toward smaller teams.

Here is why. Communication overhead scales quadratically with team size. A team of 4 has 6 communication channels. A team of 8 has 28. When each individual ships faster because of AI assistance, the team reaches its coordination limit sooner. The output added by the fifth or sixth engineer stops covering the cost of keeping everyone aligned.

Smaller teams also benefit from clearer ownership. When three people own a service, it is clear who is responsible for it. When ten people own a service, everyone assumes someone else is handling the monitoring, the tests, the deployment pipeline.

But smaller is not always better, and this is where the nuance matters.

A team that is too small for its domain will fragment its attention across too many concerns. Three engineers trying to own a payments system, a notification platform, and a data pipeline will do none of them well. They will produce code quickly with AI assistance, but they will produce the wrong code, in the wrong abstractions, because no one has the time to think deeply about any one domain.

The rule I would use is not a fixed number. It is the smallest team that can own a coherent domain end-to-end. In practice this often lands between 3 and 5 people, but the number should follow the problem boundary, not the other way around.

A team of 3 that owns a single well-bounded service is better than a team of 8 that owns six loosely related ones. But a team of 6 that owns a tightly integrated platform is better than splitting that platform across two teams of 3 that now have to coordinate across a boundary that should not exist.

Friction: Some of It Is Structural
#

The instinct when a new efficiency tool arrives is to use it to remove every source of friction. AI makes code review faster, so why not automate it? AI can summarize meetings, so why not eliminate them? AI can write documentation, so why not stop requiring it?

This instinct is partially right. A lot of process friction is pure waste. Waiting three days for a manager to approve a deployment that could be automated. Holding a 30-minute standup where eleven people say “yesterday I worked on tickets, today I will work on tickets.” Filing a Jira ticket for a one-line config change.

But some friction serves a purpose, and removing that friction silently degrades the team’s output quality over time.

Code review is the clearest example. Before AI, code review served multiple functions: catching bugs, enforcing style, sharing knowledge, and forcing the author to think about their code one more time before it shipped. AI can handle the style and formatting portion completely. It can catch obvious bugs. What AI cannot do is evaluate whether the code solves the right problem, whether the abstraction will survive the next feature request, or whether the approach is consistent with how the rest of the system works.

In fact, code review becomes more important with AI-generated code, not less. When a human writes every line, you can assume the author thought about each line at some level, even if imperfectly. When an LLM generates code, the author may not have read every line carefully. The reviewer can no longer rely on the author’s intent as a safety check. The reviewer has to verify both correctness and intent independently.

This is harder work than traditional code review. It means reviews should be slower, not faster. The process should have more friction, not less. What should change is what the friction is applied to: less time on style, more time on substance.

The same principle applies to other forms of deliberation.

Sprint planning, done well, is the moment when the team asks “are we working on the right things?” That is productive friction. Sprint planning, done poorly, is an hour of reading ticket descriptions aloud. That is waste.

Architecture discussions are productive friction when they prevent the team from building on a flawed foundation. Architecture discussions are waste when they become philosophical debates that never converge on a decision.

The discipline is in telling the difference. The test I use: does a given process force someone to think about something they would otherwise skip? If yes, keep the process, even if it feels slow. If no, automate the process or remove it.

Processes Worth Keeping
#

Beyond code review, a few processes become more valuable in an AI-accelerated environment.

Architecture decision records (ADRs). When code is cheap to produce, the cost of building on the wrong abstraction is disproportionately high. A one-page document that captures what was decided, why, and what alternatives were considered is worth more than the five minutes it takes to write. AI can draft these from a conversation, but the team still needs to have the conversation.

Incident retrospectives. Retrospectives are one of the few processes that compound knowledge over time. When a production incident occurs, the team that writes down what happened, why, and what the team will change makes fewer repeat mistakes over time. The team that fixes the bug and moves on repeats the same class of mistake forever. AI can assist with drafting the timeline from logs and alerts, but the insight about what to change has to come from the people who were in the room.

Onboarding. Onboarding is paradoxically harder in AI-heavy teams. Historically, junior engineers built deep familiarity with a codebase by writing code in it, working through its conventions, and learning its quirks through repetition. When AI handles much of the writing, that repeated work disappears, and with it, the learning. Teams need to be more intentional about how they transfer knowledge. Structured pairing, documented design decisions, and explicit mentorship become more important, not less.

Specification before implementation. The ability to write a clear specification is now the highest-leverage skill in software engineering. A precise spec turns an LLM from a mediocre pair programmer into a highly effective one. A vague spec makes the LLM hallucinate. Teams that invest in specification quality will produce more than teams that skip straight to prompting.

Processes Worth Eliminating
#

Some processes continue only out of habit. AI gives teams a reason to rethink them.

Status meetings that are not decisions. If a standup is just a round-robin of activity reports, replace the standup with an AI-generated summary of yesterday’s commits, PRs, and tickets. Reserve synchronous time for discussions that require back-and-forth. Most status updates do not require back-and-forth.

Granular task estimation. When AI can generate implementation drafts, estimating individual tasks in story points becomes less accurate and less useful. The time spent decomposing work into one-point, two-point, and three-point tickets is time not spent on the actual work. Replace granular estimation with outcome-level planning: what do we want to ship this cycle, and are we on track?

Manual test writing for boilerplate. AI handles test scaffolding well. Engineers should focus on test design (what cases matter, what edge cases exist, what invariants must hold) and let the tooling handle the mechanical work of writing assertions and setup code. The test plan is the valuable artifact. The test file is increasingly interchangeable.

Elaborate approval workflows. If a change passes CI, passes automated security scanning, and passes peer review, the change should not also need approval from a manager who has not read the code. Every gate that does not add information is pure delay.

The Structure of a Team That Gets This Right
#

Here is the team I would design for this era.

Four or five people who own a clear domain. They spend less time writing boilerplate and more time debating trade-offs. Their code reviews are rigorous about intent and brief about style. They write ADRs for non-obvious decisions and skip ADRs for obvious ones. They do not hold meetings that could be a paragraph of text. They write specifications before they prompt.

They treat AI as a way to apply more judgment, not a replacement for it. The judgment is still the team’s job. The tooling just makes acting on that judgment faster.

What Does Not Change
#

For all the shifts, some things do not change.

Trust between team members cannot be generated by a language model. Psychological safety, the ability to say “I think this approach is wrong” without fear, remains the single strongest predictor of team performance. A team of average engineers who trust each other will outperform a team of brilliant engineers who do not trust each other, with or without AI assistance.

Shared understanding of the problem domain cannot be delegated to tooling. If no one on the team deeply understands the business context, the code will be technically correct and strategically wrong, faster than ever.

And the discipline to build less, not more, remains the hardest skill. When implementation costs almost nothing in time, the temptation to overbuild is constant. The teams that thrive will be the ones where someone at the table says “we don’t need this,” and the rest of the team listens.


The case for static priorities

Most teams that adopt a P0-P5 priority scale make the same mistake: they treat priority as a ranking, not a classification. The moment all P0s are resolved, someone suggests “promoting” P1s to P0. This feels logical. It is wrong, and in this article I want to defend the alternative: a static system where a priority label describes the work itself and never moves just because the queue in front of the work emptied.

Priority is a property of the work, not its position in the queue
#

P0 means something specific: the production database is corrupted, customers cannot check out, a security vulnerability is being actively exploited. P0 work has an operational definition. The label describes the nature and consequence of the task.

A P1 task is important work that should be done this quarter: a feature the largest customer has been requesting, a refactor that will unblock two teams, a performance optimization that will cut infrastructure costs by 30%. Completing the P0s does not transform a P1 into an existential crisis. The task has not changed. The task’s urgency has not changed. Calling the task P0 adds no information and removes the ability to distinguish the task from actual emergencies.

If everything becomes P0, nothing is P0
#

Priority inflation is the fastest way to destroy a prioritization system. Once a team learns that P0 simply means “next thing we’re working on,” the label stops meaning anything. When a genuine P0 arrives, such as a corrupted production database, the team needs everyone to drop what they are doing, but the word no longer signals urgency because promotion has weakened the word.

Consider what happens when you tell a team “this is P0” in a healthy system: people stop what they are doing, context-switch, and focus on the emergency. That response depends on P0 being rare and meaningfully different from everything else. If the backlog contains forty items that were “promoted” to P0, the next genuine emergency gets the same response as a feature request.

A static system is a shared vocabulary
#

The value of a priority scale is communication. When an engineer files a bug as P1, a product manager should immediately understand the rough timeframe and severity without reading the full description. When a support rep escalates a ticket to P0, the on-call engineer should know to wake up.

This shared understanding requires stable definitions. If P0 means “site is down” on Monday but “most important remaining backlog item” on Wednesday, the label communicates nothing about the work itself, only about where the work sits relative to other work. That is what a sorted list already tells you. The priority label should add something a list cannot: the kind of impact the task has.

Why not use importance × urgency?
#

The Eisenhower Matrix (important/urgent, important/not urgent, not important/urgent, not important/not urgent) is the most common alternative to a priority scale. The matrix is appealing because the matrix is simple: two dimensions, four quadrants, done. But the matrix breaks down in practice for several reasons.

Everything a team is asked to do is important. If a task were truly unimportant, the task would not be in the backlog. As a result, most tasks cluster in the “important” row, and the entire burden of differentiation falls on the urgency axis. The matrix has effectively collapsed back to a single dimension, but with less granularity than P0-P5.

Four buckets are too coarse. Within “important and not urgent” sits everything from “refactor the authentication system this quarter” to “update the README someday.” The quadrant gives you no way to distinguish these. P0-P5 gives you six levels, each with an attached timeframe and commitment level. The matrix tells you “schedule it.” The priority scale tells you “schedule it this quarter.”

Urgency is ambiguous at the boundaries. Is a customer feature request urgent because the customer is asking today, or not urgent because there is no deadline? Is a tech debt refactor urgent because the refactor slows down every sprint, or not urgent because nothing is currently broken? These edge cases generate the same kind of debate that multi-dimensional systems create, but the matrix offers only two positions: urgent or not. P0-P5 lets you express degrees of time sensitivity without a separate axis.

The matrix is a decision framework, not a communication protocol. “Important and urgent” describes a reasoning process. P0 describes a shared organizational response: drop everything, wake people up, ship a fix today. The priority label carries both classification and implied action. The matrix carries only classification, leaving the action unspecified.

The Eisenhower Matrix is a good tool for personal time management. For team-scale prioritization of a shared backlog, a single priority scale with pre-agreed definitions communicates faster and differentiates better.

Why not decompose into impact, urgency, and risk?
#

A related objection: instead of a single P0-P5 label, why not rate each task on multiple dimensions? If each dimension only has two or three values, assessment is quick.

Assessment is indeed quick. The problem is the decision rule. Once you have impact × urgency × risk, you need a policy for how the dimensions combine. Does high impact + medium urgency + low risk come before medium impact + high urgency + low risk? Every disagreement becomes a debate about weights instead of a quick “this is P1” call.

The P0-P5 scale is that policy, decided in advance. Someone already decided the mapping. The team classifies and moves on.

That said, if a team consistently disagrees on priority labels, the disagreement itself is useful. The disagreement surfaces the underlying dimensions naturally. The debate about impact, urgency, and risk is the conversation you have when the single label fails, not the default workflow. A single priority label is the fast path. Decomposed dimensions are the escalation path.

Working on P1s does not require calling them P0
#

Here is the practical concern: after the P0s are done, what should the team work on? P1s, obviously. Nothing needs to be renamed to start working on the P1s.

A static priority system separates two decisions:

  1. What is this task? (classification: P0 through P5)
  2. What should we work on next? (scheduling: pick the highest-priority task that is unblocked and aligned with current goals)

Dynamic systems conflate the two decisions. By making priority a function of backlog state, a dynamic system loses the ability to classify independently of schedule. A task’s priority no longer tells you what kind of work the task represents. The label only records where the task ranked at the moment of labeling.

Re-labeling has a cost
#

Every time items are promoted after higher-priority work completes, the team pays a coordination cost. Someone has to decide which P1s become P0s. That decision requires a meeting, or at least a Slack thread, or a product manager’s judgment call. The larger the team, the more expensive the relabeling becomes.

In a static system, this cost is zero. The team finishes the P0s, picks up the next highest-priority task, and keeps building. No ceremony, no relabeling, no debate about whether a P1 has “earned” its promotion.

Static priorities enable proportional allocation
#

Healthy teams do not just work on the highest-priority item. These teams allocate capacity across priority levels: 10% on P0 (emergencies, when they arise), 60% on P1 (core roadmap), 20% on P2 (improvements), 10% on P3-P5 (maintenance, tech debt, experiments).

This kind of planning is only possible with stable definitions. If P1s keep becoming P0s, the team cannot reason about where the team’s time is going. Velocity metrics become meaningless because the definition of each bucket shifts under the team.

Truthful labeling forces truthful conversations
#

A static system forces the team to be realistic about what the team is not doing.

In a dynamic system, everything important eventually becomes P0, so nothing is ever explicitly deprioritized. A stakeholder asks “when will you do X?” and the answer is “it’s P1, we’ll get to it after the current P0s.” The answer is accurate but uncomfortable. The discomfort is the point.

When a P2 feature stays P2 for three quarters, the organization has a clear signal: this work is not important enough to displace P1 work. That signal triggers a useful conversation: should we increase staffing, should we drop the feature, or should we accept that the feature will not happen soon?

In a dynamic system, the same feature would have been promoted to P1, then P0, and done without anyone confronting the question of whether the feature was worth doing at all.

When priorities should change
#

A static system does not mean priorities are frozen forever. Priorities should change when the task itself changes, not when the backlog above the task empties.

A P2 performance issue becomes P1 when a major customer threatens to churn over the issue. The task’s context changed. A P3 experiment becomes P1 when early results show the experiment could replace a critical dependency. New information arrived. A P1 feature becomes P0 when a competitor launches the feature and the sales team starts losing deals. The market shifted.

These are legitimate priority changes because the nature and consequence of the work actually changed. These changes are not promotions based on queue position.

The scale
#

For reference, a well-defined static scale looks something like this:

  • P0: Critical. Production is down, data is at risk, or customers are blocked. Drop everything.
  • P1: High. Important work with clear business impact. Should be done this quarter.
  • P2: Medium. Valuable but not time-sensitive. Should be done within the next two quarters.
  • P3: Low. Nice to have. Will do if capacity allows.
  • P4: Minimal. Logged for consideration. No commitment to do.
  • P5: Rejected or deferred indefinitely. Kept for historical reference.

Each level describes a timeframe, a level of commitment, and a resource allocation. None of the definitions reference other tasks in the backlog.

What to do instead of promoting
#

When the P0s are finished and work moves on to P1s, do not relabel. Instead:

  1. Sort P1s by dependencies and impact. Within a priority level, use a secondary criterion to decide order.
  2. Pick the top P1 and start. No ceremony needed.
  3. If the P1s run out, start on P2s. The system is working as intended.

The priority label told you the task was important-but-not-critical work. It still is. The fact that nothing more urgent remains does not change the nature of the work.

The one-sentence version
#

Priority describes what a task is, not when you will get to the task. Keep the label stable, sort within levels, and let the schedule be the schedule.


The Shifting Bottleneck: From Writing Code to Questioning Everything

Every time AI removes a bottleneck in software development, the next one appears one level higher up the decision chain. The constraint never disappears, it just moves.

The Five Stages
#

1. Producing code
#

For decades, the bottleneck was typing. Writing code was slow. Understanding codebases was slow. Every feature required a human to translate intent into syntax, one keystroke at a time. We built IDEs, autocompletion, linters, and refactoring tools to speed up writing code, but the fundamental constraint remained: humans had to write the code.

Then LLMs arrived and removed that constraint almost overnight. GitHub Copilot, Cursor, Claude Code, and a dozen other tools made it trivial to generate working code from a description. The bottleneck moved.

2. Verifying generated code
#

When code writes itself, your job shifts from author to reviewer. You no longer ask “how do I implement this?” but “does this implementation do what I want?”

Verification is a fundamentally different skill. Reviewing code you didn’t write requires reading critically. Every function could contain a subtle bug, a hallucinated API call, or a plausible-sounding but incorrect assumption. The code looks right, the tests pass, but does it actually solve the problem?

The verification bottleneck is harder than the production bottleneck because verification is an underconstrained problem. There are infinitely many ways code can be wrong, and no finite checklist catches them all.

3. Deciding what to implement
#

Once you can generate and verify code quickly enough, a new question comes up: should we build this feature at all?

Not every feature deserves implementation. Not every bug deserves a fix. The cost of writing code approaches zero, but the cost of maintaining code, shipping code, and supporting code does not. Every line of code is a liability. Every feature increases the chance of bugs, increases cognitive load, and constrains future decisions.

The bottleneck shifts from “can we build it?” to “should we build it?” Deciding what to implement is product thinking, not engineering. Engineers who spent careers optimizing for execution speed now find themselves needing to develop judgment about what is worth executing.

4. Deciding what to build
#

Push further: should this product exist at all?

Before LLMs, the cost of building a product was a natural filter. If it took six months and a team of five to build an MVP, you had to be reasonably confident the market wanted it. Now that an MVP can be built in an afternoon, the cost of building is no longer the filter. The filter is the cost of being wrong about product-market fit.

The bottleneck now sits in strategy. Understanding users, identifying genuine problems, and choosing which market to enter are now the scarce skills. Anyone can build the product. Figuring out which product to build is the hard part.

5. Questioning automation itself
#

And then you arrive at the final question: should we have automated ourselves at all?

This is not a joke. Every layer of automation removes human involvement from a layer of decision-making. When code writes itself, verifies itself, decides what to build, and decides whether to build it, what is left for the human?

There is a genuine question about whether the end state of this trend is human irrelevance. Not because AI became malevolent, but because we systematically removed every reason for a human to be involved. Each step was rational. Each bottleneck existed. Each automation was justified. And yet the cumulative effect is a gradual loss of human agency in the creative process.

The Bottleneck Elevator
#

This pattern has a name in systems thinking: the theory of constraints. In any system, improving one stage of a pipeline exposes the next bottleneck downstream. In software development, the pipeline runs upward through abstraction layers:

Existential → Product → Feature → Verification → Production

AI has been climbing this stack from the bottom. Each time it solves one level, the next level becomes the limiting factor. The work doesn’t decrease, it transforms.

What to Do Next
#

If you are a software developer today, your career trajectory is moving up this stack whether or not you want it to.

If you are still at Stage 1 (producing code), you are already behind. Adopt AI coding tools now. The gap between developers who use LLMs and those who don’t is widening every month.

If you are at Stage 2 (verifying code), invest in specification skills. The ability to write precise, testable requirements is what separates effective AI-assisted developers from those who spend all day reviewing hallucinated nonsense.

If you are at Stage 3 (deciding what to implement), develop product judgment. Understanding trade-offs, opportunity cost, and the difference between “can build” and “should build” is your leverage.

If you are at Stage 4 (deciding what to build), you are in the right place for now. Strategy, market understanding, and user empathy are the current frontier.

If you are at Stage 5, you have seen the full picture. The question is not whether automation will continue climbing, it will. The question is what humans should do with the freedom that comes from not being the bottleneck.

The Answer
#

I don’t have a clean answer for Stage 5. Neither does anyone else. The optimists say we will find new creative work that we cannot yet imagine, the same way we always have after every technological revolution. The pessimists say this time is different because AI doesn’t just replace manual labor, it replaces cognitive labor, and eventually all of it. Both positions are old positions in the technological unemployment debate, and neither side has settled it.

What I can observe is that the bottleneck will keep shifting. Whatever you think the final constraint is, solving it will reveal another constraint. The work of a developer is not to write code, it is to stand at the current bottleneck and push.

The bottleneck will move. Your job is to move with it.


Code Factories: The RollerCoaster Tycoon Perspective

You start with an empty plot of land, a small bank loan, and an objective to attract 600 guests by the end of year two.

That’s RollerCoaster Tycoon. It’s also your first day as a startup founder.

In RollerCoaster Tycoon, you build a theme park from nothing. You lay down paths, construct rides, hire staff, set prices, manage finances, and try to keep thousands of guests happy simultaneously. You watch little pixelated people queue for attractions, complain about litter, get sick on rides, and leave when they’ve had enough.

Building a software product is the same game with different sprites. The question is whether your park guests are leaving happy or leaving forever.

Ride Design: Feature Development
#

In RollerCoaster Tycoon, every ride is a design problem.

A roller coaster starts as blank track on a canvas. You choose the lift hill height, the drop angle, the number of inversions, the banking on turns. Each decision affects three metrics: excitement, intensity, and nausea.

Excitement is what attracts guests. Intensity is the thrill level: some guests love it, some won’t ride. Nausea is the cost: too much and guests vomit on your paths.

Feature development has the same three metrics.

Excitement is user value, the feature that solves a genuine problem, delivers a delightful experience, or unlocks new capability. Intensity is complexity, the learning curve, the configuration surface, the cognitive load on users. Nausea is maintenance burden, the edge cases, the performance overhead, the support tickets.

A roller coaster with high excitement and moderate intensity is the ideal ride. Guests line up for it, enjoy it, and ride again. A feature with high value and manageable complexity is the ideal feature. Users adopt it, benefit from it, and recommend it.

A roller coaster with maximum intensity and maximum nausea empties the queue. Guests try it once, throw up, and never return. A feature with maximum complexity and maximum maintenance burden empties the user base. Users try it once, get confused, and never return.

The RollerCoaster Tycoon lesson: optimize for excitement, manage intensity, minimize nausea. In software: optimize for value, manage complexity, minimize burden.

Park Layout: Information Architecture
#

In RollerCoaster Tycoon, path layout determines everything.

Guests follow paths. They can only reach rides that are connected to the path network. If a ride is hidden behind another ride with no path leading to it, it might as well not exist. If the path network is a tangled mess, guests get lost, frustrated, and leave.

Your information architecture is your path layout.

Users follow navigation. They can only find features that are connected to the navigation flow. If a feature is buried three menus deep with no link to it, it might as well not exist. If the navigation is a tangled mess, users get lost, frustrated, and leave.

RollerCoaster Tycoon teaches a specific layout pattern: the hub-and-spoke.

A central plaza connects to several themed areas. Each themed area contains related rides. Signs guide guests from the hub to the areas they want. The layout is intuitive, discoverable, and scalable: add a new spoke when you add a new area.

Good information architecture is hub-and-spoke.

A homepage or dashboard connects to major feature areas. Each area contains related functionality. Breadcrumbs and search guide users to what they need. The layout is intuitive, discoverable, and scalable: add a new section when you add a new feature area.

Parks with random path layouts have low guest counts and low ratings. Products with random navigation have low engagement and low retention.

Guest Happiness: User Satisfaction
#

Every guest in RollerCoaster Tycoon has a happiness meter.

It starts high when they enter the park. Every good ride increases it. Every long queue decreases it. Every piece of litter they see decreases it. Every overpriced item decreases it. When happiness drops below a threshold, the guest leaves and the park rating drops.

User satisfaction works the same way.

Every smooth interaction increases it. Every loading spinner decreases it. Every confusing error message decreases it. Every unexpected price increase decreases it. When satisfaction drops below a threshold, the user churns and the review rating drops.

RollerCoaster Tycoon teaches that guest happiness is a composite of many small factors, not one big one.

No single ride makes guests happy. Happiness is the combination of good rides, short queues, clean paths, reasonable prices, attractive scenery, and available amenities. Fix one problem while ignoring the others and happiness barely moves.

User satisfaction is equally composite. No single feature makes users happy. Satisfaction is the combination of useful features, fast performance, clear error messages, fair pricing, good design, and responsive support. Fix one problem while ignoring the others and satisfaction barely moves.

The park with the highest rating isn’t the one with the best roller coaster. It’s the one where everything works well together. The product with the highest satisfaction isn’t the one with the best feature. It’s the one where everything works well together.

Queue Management: Performance Optimization
#

In RollerCoaster Tycoon, queues are the visual indicator of a problem.

A long queue means guests are waiting. Waiting guests are unhappy guests. Unhappy guests leave bad reviews and reduce the park rating.

The solutions are straightforward: increase ride capacity, add more rides, or optimize throughput. Run three trains instead of two. Synchronize the dispatch timing. Build a second coaster nearby to distribute the load.

Performance bottlenecks are queues.

A slow API response means requests are waiting. Waiting requests are unhappy users. Unhappy users abandon carts, close tabs, and leave bad reviews.

The solutions are equally straightforward: increase capacity, add more instances, or optimize throughput. Scale horizontally with more servers. Cache frequently requested data. Build a read replica to distribute the load.

RollerCoaster Tycoon also teaches a subtler lesson about queues: the perception of waiting matters as much as the actual wait.

Rides with themed queue lines (tunnels with decorations, TV screens playing videos, interactive elements) have higher guest satisfaction than rides with bare concrete queues, even when the wait time is identical.

The software equivalent is perceived performance.

A page that loads a skeleton UI immediately and fills in content progressively feels faster than a page that shows nothing for two seconds and then renders everything at once. The actual load time is the same. The perceived load time is dramatically different.

Theme your queue lines. Show progress indicators, skeleton screens, and optimistic updates. The wait is the same. The experience is better.

Staff Management: Team Operations
#

Your park needs staff.

Handymen sweep litter and empty trash cans. Mechanics inspect and repair broken rides. Security guards catch vandals and keep the peace. Entertainers make guests happy in queue lines.

Each staff member has a patrol zone. Set the zone too large and they can’t cover it all; litter accumulates in the corners. Set the zone too small and they cluster in one area while the rest of the park deteriorates. Hire too few and the park falls apart. Hire too many and the payroll drains your budget.

Engineering teams have the same dynamics.

SREs are mechanics: they inspect, repair, and prevent production failures. Support engineers are handymen: they clean up tickets, triage issues, and keep the user experience tidy. Security engineers are security guards: they find vulnerabilities, respond to incidents, and keep the codebase safe. Developer advocates are entertainers: they engage the community, write documentation, and make the developer experience enjoyable.

The patrol zone problem is the on-call rotation problem.

Give an on-call engineer too many services and they can’t respond to incidents fast enough; errors accumulate. Give them too few and they sit idle while other on-call engineers are overwhelmed. Too few engineers on rotation and the team burns out. Too many and context is spread thin.

RollerCoaster Tycoon also teaches that staff training matters.

A trained mechanic repairs rides faster. A trained handyman cleans more efficiently. In software, the equivalent is runbooks, incident playbooks, and onboarding documentation. A trained on-call engineer resolves incidents faster because they’ve seen the failure modes before and know the recovery steps.

Invest in your staff or watch your park deteriorate.

Ride Breakdowns: Production Incidents
#

Every ride in RollerCoaster Tycoon has a reliability rating.

New rides are highly reliable. As they age, reliability drops. The more a ride runs, the more likely it breaks down. A broken ride closes. Guests in the queue leave unhappy. The mechanic has to walk across the park, inspect the ride, and repair it.

During the downtime, the queue grows, the guests complain, and the park rating drops.

Production incidents are ride breakdowns.

New services are highly reliable. As they age, complexity accumulates. The more a service runs, the more likely an edge case triggers a failure. A failed service goes down. Users in the middle of a workflow are interrupted. The on-call engineer has to investigate, diagnose, and fix the issue.

During the downtime, error rates spike, users complain, and the product reputation drops.

RollerCoaster Tycoon’s solution to breakdowns is preventive maintenance.

Mechanics can be assigned to inspect rides before they break. Regular inspections increase reliability and reduce the frequency of breakdowns. The cost is the mechanic’s time; time spent inspecting is time not spent repairing. But the net effect is positive: fewer breakdowns, shorter downtimes, happier guests.

The software equivalent is proactive reliability engineering.

Chaos engineering, load testing, dependency auditing, and synthetic monitoring are preventive inspections. The cost is engineering time; time spent testing reliability is time not spent building features. But the net effect is positive: fewer incidents, shorter outages, happier users.

Parks that skip inspections have spectacular breakdowns at the worst moments. Services that skip reliability testing have spectacular outages during peak traffic.

Research: Technical Exploration
#

RollerCoaster Tycoon has a research system.

You allocate funding to research and over time, new rides, scenery, and improvements become available. More funding accelerates research. Less funding slows it. No funding means you’re stuck with what you have.

The tension is genuine: every dollar spent on research is a dollar not spent on building rides right now. But without research, you can’t build the rides that attract guests later.

Technology exploration is research.

Every hour spent evaluating a new framework, prototyping a new architecture, or experimenting with a new tool is an hour not spent shipping features right now. But without exploration, you can’t build the features that attract users later.

RollerCoaster Tycoon teaches that research should be funded consistently, not in bursts.

A player who sets research to maximum at the start and then cuts it to zero runs out of new content mid-game. A player who funds research at a moderate, steady rate unlocks new rides throughout the game.

The software equivalent: invest in exploration consistently, not just when the team is between projects.

A team that dedicates 10% of every sprint to technical exploration (trying new tools, reading papers, prototyping ideas) continuously discovers improvements. A team that only explores during quarterly hackathons discovers nothing for three months at a time.

Consistent research wins. Burst research wastes the intervals between bursts.

Scenario Objectives: Sprint Goals and OKRs
#

Every RollerCoaster Tycoon scenario has an objective.

“Have 600 guests in your park by the end of year two.” “Achieve a park rating of 700 by October, year three.” “Build ten roller coasters with an excitement rating above 6.00.” “Repay your loan and achieve a monthly profit of $1,000.”

The objectives are specific, measurable, and time-bound. They force you to prioritize; you can’t do everything, so you do what matters for the objective.

Sprint goals and OKRs are scenario objectives.

“Ship the new checkout flow by end of sprint.” “Reduce p95 latency below 200ms by end of quarter.” “Increase test coverage to 80% by end of year.” “Reduce onboarding time for new engineers by 50%.”

RollerCoaster Tycoon teaches something critical about objectives: the scenario ends.

When you achieve the objective, the scenario is complete. You can keep playing (most players do) but the pressure is off. The objective gave the gameplay structure. Without it, you’d build aimlessly and the park would suffer.

Sprint goals give development structure. Without them, teams build aimlessly and the product suffers.

But RollerCoaster Tycoon also teaches that the wrong objective leads to the wrong park.

An objective that demands 1,000 guests by year one forces you to build cheap, low-quality rides to attract crowds fast. The park fills up but the rating is terrible. An objective that demands a park rating of 900 forces you to build slowly and carefully. The rating is excellent but the guest count is low.

Software objectives have the same distortion effect.

An objective that measures lines of code produces lots of code. An objective that measures features shipped produces lots of features. Neither guarantees quality, user satisfaction, or business value. The metric you optimize is the park you get.

Choose your scenario objective carefully. It will determine everything you build.

Loan Management: Technical Debt
#

Many RollerCoaster Tycoon scenarios start you in debt.

You’ve borrowed money to buy the land and build the first rides. The loan accrues interest every month. The longer you carry it, the more it costs.

You can invest in rides to generate revenue and pay it off slowly. Or you can cut spending, build cheap rides, and pay it off quickly. The first strategy grows the park but increases the total interest paid. The second strategy limits growth but reduces the financial burden.

Technical debt is a loan.

You borrowed time by taking shortcuts, skipping tests, ignoring architecture, hardcoding values. The debt accrues interest every sprint. The longer you carry it, the more it costs in debugging, refactoring, and developer frustration.

You can invest in features to grow the product and pay down debt slowly. Or you can pause features, refactor aggressively, and pay it down quickly.

RollerCoaster Tycoon teaches that the optimal strategy depends on the interest rate.

A low-interest loan is fine to carry; the revenue from new rides exceeds the interest cost. A high-interest loan is an emergency: it consumes revenue faster than the park can generate it.

Low-interest technical debt (a slightly messy module that rarely changes) is fine to carry. The velocity gained by shipping features exceeds the cost of the debt. High-interest technical debt (a critical module that breaks every sprint) is an emergency. It consumes velocity faster than the team can generate it.

Know your interest rate. Manage accordingly.

Park Value: Technical Assets
#

RollerCoaster Tycoon tracks your park value, the combined worth of all your rides, scenery, and infrastructure.

It’s not the same as cash in the bank. You can have high park value and no cash if you spent everything on construction. You can have high cash and low park value if you haven’t built anything.

Park value represents what you’ve built, not what you have. It compounds: every ride you build increases the value, and the more valuable the park, the more guests it attracts, which generates more revenue for building more rides.

Technical assets are park value.

Your component library, your CI/CD pipeline, your monitoring infrastructure, your documentation, your test suite: these are rides you’ve built. They represent what you’ve constructed, not what you have in the backlog.

A team with high technical assets and an empty backlog is well positioned: they can build anything quickly. A team with low technical assets and a full backlog is in trouble: everything they try to build requires starting from scratch.

Park value compounds the same way. A park with ten rides attracts more guests than one with two, generating more revenue to build more rides. A codebase with ten reusable components ships faster than one with two, generating more time to build more components.

The RollerCoaster Tycoon lesson: invest in park value, not just cash flow. Build rides that attract guests. Build infrastructure that attracts velocity.

Scenery and Theming: Design and UX
#

In RollerCoaster Tycoon, scenery affects guest happiness.

Trees, fountains, gardens, statues, themed decorations: guests notice them. A ride surrounded by matching scenery gets an excitement bonus. A path lined with trees and benches keeps guests happy while they walk. A park with no scenery is functional but joyless.

Design and UX are scenery.

A functional interface without thoughtful design is a park without trees. Users can complete their tasks but they don’t enjoy them. There’s no delight, no personality, no sense that someone cared about the experience.

RollerCoaster Tycoon teaches that scenery is most effective when it’s themed.

A pirate-themed roller coaster surrounded by pirate scenery (ships, treasure chests, palm trees) gets a higher excitement bonus than the same ride surrounded by random, unrelated decorations. Consistency matters more than quantity.

Design systems are theming.

A consistent design system (typography, colors, spacing, components) creates a cohesive experience. Users don’t have to relearn the interface on every page. The product feels intentional, polished, professional.

A product with random design choices (different button styles, inconsistent spacing, clashing colors) is a park with random scenery. Each element might look fine on its own. Together, they’re incoherent.

Theme your park. Theme your product.

Marketing: Developer Relations and Product Marketing
#

RollerCoaster Tycoon lets you run marketing campaigns.

Advertising for the park increases guest count. You can target local, national, or international audiences at increasing cost. More marketing brings more guests, but if the park isn’t ready for them, they arrive to long queues, dirty paths, and broken rides. They leave unhappy and the marketing was wasted.

Product marketing and developer relations work the same way.

A successful marketing campaign brings users. But if the product isn’t ready (if the onboarding is rough, the documentation is thin, the performance is slow) users arrive to a bad experience. They leave unhappy and the marketing was wasted.

RollerCoaster Tycoon teaches the sequence: build the park first, then market it.

Players who spend their initial budget on marketing instead of rides attract guests to an empty park. The guests find nothing to do and leave. The money is gone and the park rating is worse than if they’d never marketed at all.

Startups learn this the hard way. Marketing before product-market fit brings users to a product that doesn’t serve them. The users leave, the analytics look terrible, and the brand is damaged.

Build the ride first. Then put up the billboard.

Terrain Constraints: Legacy Systems
#

Some RollerCoaster Tycoon scenarios give you a blank, flat parcel of land. These are the easy scenarios. You build what you want, where you want, with no constraints.

Most scenarios aren’t like that.

You get a mountainous plot with a river running through it. Or a partially built park with existing paths and rides you can’t remove. Or an underground cavern with limited space. The terrain constrains every decision. You can’t build a flat coaster on a mountain. You can’t extend a path across a river without a bridge. You have to work with what you’ve got.

Legacy codebases are constrained terrain.

The ideal software project starts from nothing: blank repository, no dependencies, no constraints. Most projects aren’t like that.

You inherit a codebase with a decade of decisions baked in. A database schema that can’t be changed without migrating millions of rows. An API contract that external clients depend on. A build system that nobody fully understands. The legacy constrains every decision.

RollerCoaster Tycoon teaches that constrained terrain isn’t a disadvantage: it’s a design prompt.

The best coasters in the game are built on mountains, using the elevation changes to create drops and turns that flat terrain can’t support. The constraint forces creativity. The result is better than what flat terrain would have produced.

The best software architectures emerge from constrained codebases. The legacy forces you to think carefully about abstractions, boundaries, and migration paths. The result is often more robust than a greenfield design that never faced actual constraints.

Don’t wish for flat terrain. Build better coasters on the mountain.

Ride Synchronization: Service Coordination
#

In RollerCoaster Tycoon, you can build ride sequences.

A log flume that ends near a roller coaster entrance. A transport ride that shuttles guests from the back of the park to the front. A path that naturally guides guests through a series of attractions in order.

When rides are well-connected, guests flow through the park naturally, experiencing each attraction in sequence. When rides are disconnected, guests wander, backtrack, and miss attractions they would have enjoyed.

Microservice coordination is ride synchronization.

A well-orchestrated service mesh lets data flow through the system naturally, each service processing requests in sequence. A poorly connected architecture forces data to backtrack, retry, and miss services that should have been involved.

RollerCoaster Tycoon also teaches that you can’t force guests through a sequence.

You can build a path that leads from Ride A to Ride B, but if the guest is tired, hungry, or has already ridden Ride A, they’ll skip Ride B and look for food instead. The sequence must be optional, not mandatory.

Service orchestration must be the same. Each service should work independently. Coordination is an optimization, not a requirement. If Service A is down, Service B should still function, even if degraded.

Guests who are forced through a sequence they don’t want rebel. Services that are forced into tight coupling fail together.

Build the path. Let the guest choose.

The Late Game: Software Factories
#

In RollerCoaster Tycoon’s late game, something shifts.

Your park is established. Rides are built, staff is hired, research is funded, guests are flowing. You stop placing individual track pieces and start thinking about the park as a system.

You notice that the path near the pirate coaster always gets congested, so you widen it. You notice that guests cluster near the food court at noon, so you add another food stall. You notice that the park rating dips every time it rains, so you build more indoor attractions.

You’re no longer building rides. You’re optimizing a system.

Software factories are the late game of development.

Your CI/CD pipeline is established. Your monitoring is running. Your team is staffed, your architecture is sound, your users are flowing. You stop writing individual functions and start thinking about the development process as a system.

You notice that deploys always cause a spike in errors, so you add automated smoke tests. You notice that code review is the bottleneck, so you add AI-assisted review tooling. You notice that onboarding takes too long, so you build interactive tutorials.

You’re no longer writing code. You’re optimizing a system that writes code.

In RollerCoaster Tycoon, the late game players build mega-parks, sprawling empires with dozens of rides, thousands of guests, and perfect ratings. They didn’t get there by building one perfect ride. They got there by building a system where every ride supports every other ride.

In software, the late game teams build software factories, sprawling systems where AI agents write code, tests run automatically, deployments ship continuously, and incidents heal themselves. They didn’t get there by writing one perfect feature. They got there by building a system where every tool supports every other tool.

The park guest who walks into a mega-park sees rides. The developer who walks into a software factory sees features. Neither sees the decades of system optimization that made it all possible.

The Lesson
#

RollerCoaster Tycoon is a game about building something that delights people.

You start with empty land and an objective. You end with a park that thousands of guests enjoy. The satisfaction isn’t in any single ride: it’s in the system that makes every ride better.

Software development is the same game.

You start with an empty repository and a requirement. You end with a product that thousands of users rely on. The satisfaction isn’t in any single feature: it’s in the system that makes every feature better.

In RollerCoaster Tycoon, the parks that endure aren’t the ones with the tallest coaster or the most rides. They’re the ones where everything works together: the paths flow, the queues are short, the staff is attentive, the scenery is beautiful, and every guest leaves happy.

In software, the products that endure aren’t the ones with the most features or the latest technology. They’re the ones where everything works together: the architecture is clean, the performance is fast, the support is responsive, the design is consistent, and every user leaves satisfied.

If you’re still building rides one track piece at a time, it’s time to think about the park. If you’re still writing features one line at a time, it’s time to think about the factory.

The park is open.

See also
#


Code Factories: The Stock Market Perspective

You open your terminal, pull the latest commit, and run the test suite.

Some pass. Some fail. You investigate.

That’s software development. It’s also trading.

In the stock market, you analyze data, place orders, and manage risk. You develop strategies, backtest them, and deploy capital. You react to earnings reports, economic data, and competitor moves. The market never sleeps. The ticker never stops.

Building a software factory is the same game with different instruments. The question is whether you’re trading or being traded.

IPO: Your First Deploy
#

A company goes public through an initial public offering. Months of preparation (financial audits, legal filings, roadshow pitches) all leading to the moment the ticker appears on an exchange and anyone can buy shares.

Your first production deploy is your IPO.

You’ve spent months writing code, running tests in staging, doing code reviews. None of it matters until users touch the software in production. The moment you deploy to production, you’re public. Users will do things you never expected. Edge cases you never imagined will surface. The market (your users) will render its verdict.

Companies that rush their IPO get hammered. The valuation drops. The stock tanks. The reputation suffers. Teams that rush their first deploy get paged at 3 AM. The errors spike. The users complain. The reputation suffers.

The lesson is the same in both: prepare thoroughly, but don’t wait for perfection. There is never a perfect time to go public. There is never a perfect time to ship. The companies that wait too long get beaten by faster competitors. The teams that wait too long get beaten by faster teams.

Ship. Then iterate.

Market Capitalization: Lines of Code
#

A company’s market cap (share price times shares outstanding) is the simplest measure of its size. It tells you how much the market values the company right now. It tells you nothing about whether the company is well-run, profitable, or sustainable.

Lines of code is the market cap of software.

It tells you how big the codebase is. It tells you nothing about whether the code is well-written, tested, or maintainable. Just as a company with a billion-dollar market cap can be hemorrhaging cash, a codebase with a million lines can be hemorrhaging bugs.

Investors learned long ago that market cap is a starting point, not a conclusion. They look deeper: revenue, margins, growth rate, debt levels. Software teams need to look deeper too: test coverage, deployment frequency, mean time to recovery, change failure rate.

The stock that doubles on hype and collapses on earnings is the codebase that doubles in size on features and collapses under technical debt.

Bull and Bear Markets: Feature Velocity
#

Bull markets are euphoric. Stocks go up. Everyone is a genius. Companies raise money easily, hire aggressively, and expand recklessly. Risk appetite is high. Valuations detach from fundamentals.

Bear markets are brutal. Stocks go down. Nobody is a genius. Companies lay off workers, cut projects, and conserve cash. Risk appetite vanishes. Valuations collapse below reasonable levels.

Software projects have the same cycles.

In a bull phase, the product is winning. Features ship fast. Users are happy. Management approves every hire and every request. The team grows. Velocity increases. Everything feels possible.

In a bear phase, the product is struggling. Bugs pile up. Deadlines are missed. Hiring freezes. The team shrinks. Velocity decreases. Everything feels impossible.

The best investors don’t change their strategy based on the market. They have a strategy that works in both environments. They buy quality companies in bull markets and hold them through bear markets. They use bear markets to accumulate positions in companies they believe in.

The best software teams do the same. They invest in infrastructure during bull phases so they can survive bear phases. They maintain code quality when it’s tempting to cut corners. They use slow periods to pay down technical debt, refactor, and build the foundation for the next bull run.

Teams that only build features during the good times and cut everything during the bad times are like investors who buy at the top and sell at the bottom.

Diversification: Your Tech Stack
#

Financial advisors agree on almost nothing, but they agree on this: diversify your portfolio. Don’t put all your money in one stock, one sector, or one asset class. If that one bet fails, you lose everything.

Technology diversification works the same way.

A team that only knows one language, one framework, and one database is a portfolio with one stock. If that technology becomes obsolete, the team becomes obsolete. If that framework has a critical vulnerability, every service is vulnerable. If that database hits a scaling wall, every feature hits the wall.

But diversification has a cost. A portfolio with 200 stocks is harder to manage than one with 20. A codebase with 15 programming languages is harder to maintain than one with 3.

The best investors hold enough positions to be diversified but few enough to understand each one deeply. The best software teams use enough technologies to avoid single points of failure but few enough to develop deep expertise in each.

The index fund approach (owning a little bit of everything) works in finance. In software, it’s called using industry standards: well-supported languages, proven databases, widely adopted frameworks. You don’t pick the most exciting technology. You pick the one that won’t go to zero.

Short Selling: Technical Debt
#

Short selling is borrowing a stock, selling it, and hoping the price drops so you can buy it back cheaper. You profit from decline. The risk is unlimited; the stock can theoretically go to infinity, and your losses grow with it.

Taking on technical debt is short selling your future productivity.

You borrow time now (by skipping tests, ignoring architecture, hardcoding values) and you pay it back later with interest. If the project gets cancelled or rewritten before the debt comes due, you profit. If the project succeeds and grows, the debt compounds. Every feature becomes harder to ship. Every bug becomes harder to fix. Every new engineer takes longer to onboard.

Like short selling, technical debt can be rational. Traders short overvalued stocks. Engineers take on debt when the feature must ship this week or the company might not exist next month. The trade is calculated: the survival benefit of shipping now outweighs the cost of the debt later.

But just as most short sellers lose money in the long run, most teams that habitually take on technical debt ship slower in the long run. The interest payments (debugging, refactoring, working around hacks) consume an ever-larger share of development time.

The markets have a saying: the market can remain irrational longer than you can remain solvent. Software has an equivalent: the codebase can remain broken longer than your team can remain patient.

Dividends: Documentation and Tests
#

Dividends are payments a company makes to its shareholders from its profits. They’re not flashy. They don’t make headlines. But they compound. A stock that pays a 3% dividend and reinvests it grows dramatically over decades. Dividends are the quiet engine of long-term wealth.

Documentation and tests are dividends.

They don’t ship features. They don’t impress stakeholders in demos. But they compound. Every test written makes the next refactor safer. Every document written makes the next onboarding faster. Every API description written makes the next integration smoother.

Companies that cut dividends to fund growth sometimes succeed. More often, they lose the trust of long-term investors and find it harder to raise capital when they need it. Teams that skip documentation and tests to ship features sometimes succeed. More often, they lose the ability to make changes confidently and find every feature takes longer than the last.

The best dividend stocks aren’t the ones with the highest yield. They’re the ones with the most consistent, most reliable, most growing dividends. The best codebases aren’t the ones with the most tests. They’re the ones with the most consistent, most reliable, most growing test coverage.

Compound interest is the eighth wonder of the world. Compound documentation is the ninth.

Market Orders vs Limit Orders: Deployment Strategies
#

A market order says: buy this stock now, at whatever price the market is offering. Fast. Guaranteed execution. No price guarantee.

A limit order says: buy this stock, but only if the price is at or below my limit. Slower. Not guaranteed to execute. Price guaranteed.

A market order is deploying to production on a Friday afternoon. Fast. The feature is live. No safety guarantee.

A limit order is deploying through a canary release: route 5% of traffic to the new version, monitor for errors, and only proceed if everything looks good. Slower. Not guaranteed to finish today. Safety guaranteed.

Most retail investors use market orders because they’re simple and immediate. Most retail developers deploy all at once because it’s simple and immediate.

Professional traders use limit orders to control their risk. Professional engineering teams use progressive deployments to control theirs.

The stock that gaps down 10% on bad earnings is the deploy that errors 10% of requests on a bad commit. The trader who used a limit order avoided the gap. The team that used a canary deployment avoided the outage.

Earnings Season: Sprint Reviews
#

Every quarter, public companies report earnings. Revenue, profit, guidance. Analysts dissect every number. The stock moves based on whether the results meet, beat, or miss expectations.

It doesn’t matter whether the company is doing well in absolute terms. It matters whether the company is doing well relative to expectations. A company that doubles revenue but was expected to triple it sees its stock drop. A company that loses money but loses less than expected sees its stock rise.

Sprint reviews are earnings season.

Every sprint, the team reports what it shipped. Stakeholders dissect every feature. Morale moves based on whether the results meet, beat, or miss expectations.

It doesn’t matter whether the team is productive in absolute terms. It matters whether the team is productive relative to expectations. A team that ships five features but committed to seven feels like it failed. A team that ships three features but committed to two feels like it succeeded.

The earnings lesson for companies is straightforward: underpromise and overdeliver. The sprint lesson for teams is the same. The company that guides conservatively and beats expectations rewards its shareholders. The team that commits conservatively and beats its sprint rewards its morale.

Companies that inflate guidance to pump the stock eventually miss and crash. Teams that inflate commitments to impress stakeholders eventually miss and burn out.

Volatility: Production Incidents
#

Volatility is how much a stock’s price moves up or down. High volatility means large, unpredictable swings. Low volatility means small, predictable movements.

Traders love volatility: it creates opportunity. Investors hate volatility: it creates anxiety. Both are describing the same phenomenon from different perspectives.

Production incidents are volatility.

High incident volatility means the system swings between working and broken with little warning. The on-call engineer gets paged at random hours. The deploy button feels like a slot machine. Nobody knows whether today will be a quiet day or a fire-fighting day.

Low incident volatility means the system is stable and predictable. Deploys are boring. Pages are rare. The team can plan its work with confidence.

The paradox of volatility is that it’s both a risk and a reward. Traders who can predict volatile movements profit handsomely. Engineers who can diagnose and fix incidents quickly become heroes. But predictability is worth more than heroism. The trader who consistently earns 10% a year beats the trader who swings between +50% and -40%. The team that consistently ships without incidents beats the team that heroically recovers from weekly outages.

The VIX (the stock market’s volatility index) is often called the fear index. Your incident frequency is your VIX. If it’s high, your team is afraid to deploy. If it’s low, your team deploys confidently.

Fundamental Analysis vs Technical Analysis: Code Reviews
#

Fundamental analysts study the company, its financials, its management, its competitive position, its growth prospects. They believe the stock price will eventually reflect the company’s true value.

Technical analysts study the chart, price movements, volume, trends, patterns. They believe the price already reflects everything worth knowing and the pattern predicts what comes next.

Both approaches have merits. Both have blind spots.

Code review has the same split.

The fundamental reviewer reads every line, understands the design decisions, evaluates the architecture, and checks whether the implementation matches the specification. They’re thorough but slow.

The technical reviewer looks at the diff, the changes, the patterns, the test coverage, the style. They’re fast but can miss design flaws that aren’t visible in the diff.

The best investors combine both. They use technical analysis for timing and fundamental analysis for selection. The best code reviewers combine both too. They look at the diff for quick feedback and the architecture for deeper understanding.

Investors who only use technical analysis get caught in pump-and-dump schemes. Reviewers who only look at diffs get caught by changes that look correct in isolation but break the system in context.

Insider Trading: Knowledge Silos
#

Insider trading is trading on information that isn’t public yet. It’s illegal in finance because it’s unfair: the insider has an advantage that ordinary investors don’t.

Knowledge silos are insider trading in software.

The engineer who has been on the team for five years and knows every quirk of the legacy system has information that new team members don’t. They can diagnose problems faster, estimate tasks more accurately, and avoid pitfalls that would trap anyone else. They have an unfair advantage.

Unlike the stock market, this isn’t illegal. But it is dangerous.

When the insider leaves (the senior engineer quits, the architect retires, the founding developer moves on) the team loses access to all that private information. The stock drops. The system becomes incomprehensible.

The solution in finance is regulation: insiders must disclose their trades. The solution in software is documentation: experts must disclose their knowledge. Run books, architecture decision records, onboarding guides, code comments: these are the SEC filings of software. They give everyone the same information.

Teams that don’t document are teams where insider trading is the norm. And when the insider leaves, everyone else inherits the undocumented system.

Market Crashes: Cascading Failures
#

On October 19, 1987, the Dow Jones dropped 22.6% in a single day. No single event caused it. Program trading, portfolio insurance, and panic selling created a feedback loop. Each drop triggered more selling, which triggered more drops. The system destroyed itself through its own feedback loop.

Cascading production failures are market crashes.

A database slows down, which causes API timeouts, which cause retry storms, which overload the load balancer, which drops health checks, which trigger auto-scaling, which overwhelms the database further. No single component failed. The system destroyed itself through its own feedback loop.

The 1987 crash led to circuit breakers, automatic trading halts when the market drops too fast. They don’t prevent the underlying problem. They prevent the feedback loop. They give humans time to intervene.

Circuit breakers in software work the same way. Rate limits prevent retry storms. Bulkheads prevent failures from spreading between services. Timeouts prevent cascading hangs. They don’t prevent the database from being slow. They prevent the slowness from becoming an outage.

Markets without circuit breakers crash faster and recover slower. Systems without circuit breakers fail faster and recover slower.

Index Funds: Standardized Infrastructure
#

Index funds track a market index, the S&P 500, the NASDAQ, the total market. They don’t try to beat the market. They try to be the market. Low fees. Broad diversification. Consistent returns. They’ve outperformed most actively managed funds over any reasonable time period.

Standardized infrastructure is the index fund of software.

You don’t try to build the most optimized database layer, the most custom CI/CD pipeline, the most custom-built deployment system. You use the standard: Kubernetes for orchestration, GitHub Actions for CI/CD, Terraform for infrastructure as code. Not because they’re the best in every dimension. Because they’re good enough in every dimension and they free your team to focus on what actually differentiates your product.

Actively managed funds charge higher fees for the promise of beating the market. Most don’t. Custom infrastructure demands more maintenance for the promise of better performance. Most of the time, it isn’t worth it.

The index fund lesson: stop trying to be clever. Accept market returns and spend your energy where it matters. The infrastructure lesson: stop building custom solutions for solved problems. Accept industry standards and spend your engineering effort on the product.

Warren Buffett’s advice to most investors is to buy a low-cost index fund and do nothing. The equivalent advice to most engineering teams is to adopt standard tooling and focus on shipping.

Margin Trading: Overcommitment
#

Margin trading is borrowing money from your broker to buy more stock than you can afford. It amplifies gains when you’re right. It amplifies losses when you’re wrong. If the position moves against you enough, you get a margin call: the broker demands more collateral or forces you to sell at a loss.

Overcommitting your team is margin trading.

You borrow future capacity (by promising more features than the team can deliver) to please stakeholders now. When the estimates are right, the team delivers and everyone is happy. When the estimates are wrong (and they’re often wrong) the team gets a margin call. Deadlines are missed. Quality drops. People burn out.

The margin call in software is the sprint where nothing ships. The team spent two weeks paying back the borrowed time from the previous three sprints. They were debugging instead of building. Refactoring instead of featuring. Recovering instead of advancing.

Professional traders use margin carefully, with strict risk limits. Professional engineering managers use overcommitment carefully, with buffers and contingency plans.

Amateur traders get wiped out by margin calls. Amateur teams get burned out by death marches.

Warren Buffett: The Principal Engineer
#

Warren Buffett has been investing for over seven decades. He doesn’t trade. He doesn’t use leverage. He doesn’t chase trends. He reads annual reports, understands businesses deeply, and makes a few large, concentrated bets on companies he understands.

Then he waits.

His holding period, he famously said, is forever. His edge isn’t speed or information. It’s patience and judgment.

The principal engineer is the Warren Buffett of software.

They don’t chase the latest framework. They don’t rewrite systems that work. They read codebases deeply, understand the architecture completely, and make a few large, concentrated decisions about the systems that matter most.

Then they wait.

Their edge isn’t typing speed or commit frequency. It’s judgment and patience. They know which changes are worth making and which changes will look foolish in six months. They know which abstractions will last and which will be torn out next quarter.

Buffett’s company, Berkshire Hathaway, has outperformed the market for decades by doing less, not more. The principal engineer outperforms the rest of the team not by writing more code, but by writing the right code.

The market rewards activity in the short term and wisdom in the long term. Software rewards the same.

Market Efficiency: The Myth of the Perfect System
#

The efficient market hypothesis says that stock prices reflect all available information. If a stock is undervalued, someone will buy it and push the price up. If it’s overvalued, someone will sell it and push the price down. In an efficient market, you can’t consistently beat the market because the market already knows everything you know.

Markets aren’t perfectly efficient. They’re efficient enough to humiliate most people who think they can beat them.

Software systems aren’t perfectly reliable. They’re reliable enough to humiliate most teams who think they’ve eliminated all failure modes.

The efficient market hypothesis teaches investors to be humble. No matter how smart you are, the collective wisdom of millions of market participants has already been priced in. Your edge, if you have one, is small and temporary.

The software equivalent teaches engineers to be humble. No matter how well you design the system, the collective chaos of users, dependencies, networks, and hardware will find the gaps. Your reliability, if you’ve achieved it, is temporary and requires constant maintenance.

Investors who believe they’ve found a system that can’t lose discover that markets change. Engineers who believe they’ve built a system that can’t fail discover that failure modes evolve.

The market adapts. The system adapts. Stay humble or get humbled.

The Late Game: Algorithmic Trading
#

In modern markets, the majority of trading volume comes from algorithms. Machines that analyze data, identify patterns, and execute trades in microseconds. They don’t sleep. They don’t panic. They don’t get greedy or fearful. They follow their programming with mechanical precision.

The humans who built these algorithms don’t trade. They design the systems that trade. They monitor the systems, adjust parameters, and intervene when the algorithms encounter situations they weren’t designed for. But they don’t place orders themselves.

Software factories are algorithmic trading.

AI agents write code, run tests, deploy services, and respond to incidents with increasing autonomy. They don’t get tired. They don’t get bored. They don’t introduce off-by-one errors at 5 PM on a Friday.

The engineers who built these systems don’t write code for production. They design the systems that write code. They monitor the agents, adjust prompts, and intervene when the AI encounters problems it wasn’t trained for. But they don’t write the production code themselves.

Algorithmic trading didn’t eliminate human traders overnight. The first algorithms were simple: arbitrage, market making, basic pattern recognition. They handled the routine and left the complex decisions to humans. Over time, the algorithms got better and handled more of the complexity.

Software factories won’t eliminate human engineers overnight. The first AI agents are simple: boilerplate generation, test writing, basic bug fixes. They handle the routine and leave the complex decisions to humans. Over time, the agents will get better and handle more of the complexity.

The traders who survived the algorithmic revolution were the ones who learned to work with the algorithms, not against them. They moved up the stack, from executing trades to designing strategies. The engineers who will survive the AI revolution are the ones who learn to work with AI agents, not against them. They’ll move up the stack, from writing code to designing systems.

The Lesson
#

The stock market is a system for allocating capital to its most productive use. Prices emerge from millions of participants making independent decisions based on incomplete information. Nobody controls the market. Everyone participates in it.

Software development is a system for allocating engineering effort to its most productive use. Priorities emerge from teams making decisions based on incomplete information about users, technology, and the future. Nobody controls the outcome. Everyone contributes to it.

The market doesn’t care about your feelings. Your codebase doesn’t care about your intentions. Both respond to what you do, not what you mean to do.

In both domains, the winners are the ones who manage risk, compound knowledge, stay humble, and play the long game. The losers are the ones who chase hype, take on debt they can’t repay, confuse activity with progress, and believe this time is different.

This time is never different.

The market remains. The codebase remains. The best you can do is show up every day, make good decisions, and let compounding do its work.

If you’re still hand-trading in a world of algorithms, it’s time to automate. If you’re still hand-coding in a world of software factories, it’s time to build.

The market is open.

See also
#