← Better Programmer course
06
Advanced
3 hours 15 minutes

Engineering Judgment and Trade-offs

Turn vague requests into evidence-based decisions, control unnecessary complexity, and communicate uncertainty without losing momentum.

Learning outcome

You will be able to frame problems, choose proportionate solutions, manage debt and automation, estimate through uncertainty, and document decisions at the right level.

Jump to a chapter
  1. 1. Frame the Problem Before Choosing a Solution
  2. 2. Choose the Simplest Sufficient Solution
  3. 3. Understand Duplication Before Removing It
  4. 4. Technical Debt as a Trade-off
  5. 5. Automate Repeated, Stable Work
  6. 6. Recognize Overengineering
  7. 7. Estimate Work by Exposing Uncertainty and Risk
  8. 8. Decision Records and Reversible Choices
  9. Applied exercise
  10. Knowledge check

Chapter 1

Frame the Problem Before Choosing a Solution

Good engineering starts by making the problem small enough to reason about and important enough to solve.

Problem framing states who is affected, what outcome is failing, how often it happens, and what constraints matter. A request such as “make the dashboard faster” is not yet a useful problem because it does not name the slow action, target, or user impact.

Separate observations from assumptions. Collect a baseline, trace the current flow, and ask what would count as success. Constraints include time, compliance, team skills, compatibility, and operational capacity. They shape the solution and should be written before options are compared.

Instead of “replace the frontend framework,” a useful frame may be “p95 navigation to the orders screen is 4.2 seconds for mobile users; most time is spent in an uncached API; the target is below 1.5 seconds without changing order semantics.”

Key insight

A precise problem statement prevents a technically impressive answer to the wrong question.

How to apply this

  • Name the user, failed outcome, baseline, and target.
  • Write assumptions separately and identify how to test them.
  • Record constraints before evaluating solutions.
Scenario — Rewrite proposed for a slow page

Situation

The team assumes React is the cause without measuring the request path.

Decision

Trace the journey, find the API bottleneck, and frame a measurable latency problem before choosing implementation work.

Chapter 2

Choose the Simplest Sufficient Solution

The simplest solution is the least complex option that meets today’s explicit needs with an acceptable path to change.

Simple does not mean careless or temporary. It means fewer moving parts, fewer failure modes, and less knowledge required to operate the result. “Sufficient” protects correctness, security, reliability, and expected growth; those requirements cannot be removed merely to write less code.

Compare options against the framed problem. Start with configuration, a query or index, a clear module, or a managed service before adding distributed coordination. Define a threshold that would justify a more complex design later so the decision is deliberate rather than permanent by accident.

If a single database handles expected traffic with headroom, correct indexes and a backup plan may be better than immediate sharding. The team can monitor data size and write latency and revisit when agreed limits approach.

Key insight

Complexity must pay for a named requirement now, not a possible requirement with no evidence.

How to apply this

  • List the requirement each moving part satisfies.
  • Prefer reversible steps and explicit review thresholds.
  • Count operational and team costs, not only coding time.
Scenario — Microservices for a small product

Situation

Three developers propose six services before the domain and traffic are stable.

Decision

Use clear modules in one deployable system and extract a service only when an independently changing or scaling boundary is proven.

Chapter 3

Understand Duplication Before Removing It

Duplication means similar knowledge appears in more than one place, but not every repeated line represents the same rule.

Harmful duplication causes one business change to require several edits that must stay consistent. Coincidental similarity looks alike today but belongs to different concepts and may change independently. Merging those cases creates coupling and conditional-heavy abstractions.

Wait until the shared rule and variation are visible. Then extract the smallest stable idea and keep context-specific behavior outside it. Sometimes writing a second clear implementation is safer than forcing a generic helper after the first example.

Two tax calculations for invoice and refund may share rounding but differ in legal timing. Extract the money-rounding rule, not one universal “financial operation” function.

Key insight

Remove duplicated knowledge when it creates change risk; do not remove resemblance for its own sake.

How to apply this

  • Ask whether the copies must change together.
  • Extract stable domain knowledge, not every common sequence.
  • Accept small duplication when it preserves independent change.
Scenario — Universal form component

Situation

Several forms share layout, so one component gains dozens of flags for unrelated workflows.

Decision

Share simple field and layout primitives while keeping each workflow’s validation and state explicit.

Chapter 4

Technical Debt as a Trade-off

Technical debt is future engineering cost created by a past decision that made delivery easier or faster.

Debt can be deliberate, accidental, or no longer relevant. Like financial debt, it matters through interest: slower changes, more incidents, poor onboarding, or blocked product options. Calling all imperfect code debt makes prioritization impossible.

Record the shortcut, reason, affected area, risk, owner, and trigger for repayment. Prioritize debt by product and operational impact rather than discomfort. Some debt should be paid during the next change in that area; some deserves a dedicated risk-reduction project.

A manual billing reconciliation may be acceptable for ten customers but dangerous at ten thousand. The repayment trigger can be volume, error rate, or support hours—not an arbitrary date.

Key insight

Manage debt through visible cost and triggers, not shame or a vague cleanup backlog.

How to apply this

  • Describe the concrete interest the team is paying.
  • Attach repayment to a measurable trigger or upcoming change.
  • Include prevention or verification in the repayment plan.
Scenario — Old dependency debate

Situation

The team wants a rewrite because a library feels dated, but there are no incidents or blocked changes.

Decision

Measure security, support, performance, and delivery impact before choosing upgrade, containment, or replacement.

Chapter 5

Automate Repeated, Stable Work

Automation turns a known repeatable process into a reliable executable check or action.

Good automation removes toil, reduces variance, and shortens feedback. It works best after the process is understood. Automating a confusing or changing workflow can make mistakes happen faster and hide who is responsible.

Choose tasks that are frequent, deterministic, costly, or safety-critical. Start with a small script or CI check, make failures actionable, and keep an escape path. The automation itself needs ownership, tests, and monitoring when it controls production.

Automating schema compatibility in CI can prevent breaking releases on every pull request. Fully automating an unclear emergency migration process would be dangerous until steps and recovery are tested.

Key insight

Automate proven decisions and feedback loops before automating judgment that the team has not defined.

How to apply this

  • Measure frequency, error cost, and time before investing.
  • Make automated failures explain the next action.
  • Assign ownership and verify the automation periodically.
Scenario — Flaky release script

Situation

A script deploys quickly but sometimes skips database checks and nobody trusts it.

Decision

Define the release contract, test each stage, fail closed with useful errors, and record every deployment action.

Chapter 6

Recognize Overengineering

Overengineering adds flexibility, scale, or indirection beyond the requirements and evidence available.

Common signals are generic frameworks with one use, distributed systems without scale pressure, many configuration options no user requested, and long design work for reversible details. The cost appears in learning, testing, debugging, deployment, and future change.

Ask what concrete failure the complexity prevents and what simpler option was rejected. Use a complexity budget: each new service, datastore, abstraction, or asynchronous path needs an owner and operational reason. Reversible choices should receive less analysis than irreversible ones.

A feature-flag service may be justified for many teams and audited rollouts. For one temporary boolean in one application, a simple configuration value with an expiry may be enough.

Key insight

Design for the next credible change, not every imaginable future.

How to apply this

  • Require evidence for new operational components.
  • Match design effort to decision reversibility.
  • Delete speculative extension points when they obscure current behavior.
Scenario — Plugin system before users

Situation

A product builds a dynamic plugin platform although only one fixed integration exists.

Decision

Implement the known integration behind a narrow interface and revisit extensibility when a second real case appears.

Chapter 7

Estimate Work by Exposing Uncertainty and Risk

An estimate is a forecast made from incomplete information, not a promise that controls reality.

Break work into outcomes and slices, then identify unknowns, dependencies, integration risk, and verification effort. Use ranges when uncertainty is meaningful. A precise number without evidence hides uncertainty rather than removing it.

Reduce the largest uncertainty with a short investigation, prototype, or dependency conversation. Communicate assumptions and confidence, and update the forecast when evidence changes. Include rollout, migration, monitoring, and coordination—not only coding.

A payment integration may take three to six weeks depending on provider approval and reconciliation rules. Separate the controllable implementation from external lead time and create an early contract spike.

Key insight

A useful estimate makes risk visible and guides decisions; it does not create false certainty.

How to apply this

  • State scope, assumptions, range, and confidence together.
  • Investigate the uncertainty that could change the plan most.
  • Re-estimate openly when facts change.
Scenario — Two-week promise without discovery

Situation

A team promises a migration before inspecting data quality and external dependencies.

Decision

Offer a range, run a data audit, confirm owners, and provide staged milestones with decision points.

Chapter 8

Decision Records and Reversible Choices

A decision record captures important context, options, trade-offs, and consequences so the team can understand and revisit a choice.

A short ADR or RFC should state the problem, constraints, options considered, decision, status, and follow-up signals. It is not a long defense of the author. The record preserves why an option was reasonable at the time, even if later evidence changes it.

Classify decisions by reversibility. A library behind a small adapter is easier to replace than a public API or data partition key. Reversible decisions should move quickly with measurement; hard-to-reverse decisions need broader review, migration planning, and stronger evidence.

For cache technology, the team can record freshness needs, workload, chosen managed service, rejected alternatives, cost threshold, and a review trigger if latency or availability misses the target.

Key insight

Document decisions in proportion to their blast radius and cost of reversal.

How to apply this

  • Record context and rejected options, not only the final answer.
  • Name review triggers and owners.
  • Spend more review time on irreversible and wide-impact choices.
Scenario — Architecture knowledge disappears

Situation

Six months later nobody knows why a queue was added or whether it is still needed.

Decision

Write a concise ADR linked to metrics and revisit it when the original traffic assumption changes.

Applied exercise

Write a decision brief for a contested technical change

Choose a real proposal such as a rewrite, service extraction, new datastore, or automation project and evaluate it from problem to review trigger.

  1. 01Write the user or operational problem, baseline, target, assumptions, and constraints.
  2. 02Compare at least three options, including the simplest sufficient option.
  3. 03Describe complexity, debt, delivery risk, and operational ownership for each option.
  4. 04Estimate in a range and identify one investigation that reduces the largest uncertainty.
  5. 05Record the recommendation, reversibility, rollout evidence, and review trigger.

Deliverable

A two-to-four-page decision brief or ADR that a mixed product and engineering group can review.

Knowledge check

Check your understanding before finishing

Answer from the reasoning and trade-offs, not keyword memory. After submitting, review the explanation for every question.

Q1What belongs in a useful problem frame?
Q2When is some duplication acceptable?
Q3How should technical debt be prioritized?
Q4What makes an estimate responsible?
Q5Which decision deserves the most review?