📁 Sangam Dhakal - Portfolio v2.0 - Netscape Navigator 4.0
_
□
×

📚 Interview question

What is YAGNI, and how do you balance it against designing for future requirements?

You Aren't Gonna Need It: costs of building speculative features, the difference between speculative features and keeping options open, and reversible versus irreversible decisions.

Mid-Level Theory Intermediate agile design-principles yagni

🎯 Key points

  • Do not build features or extension points until there is a real need
  • Speculative work has costs: build, delay, carry and repair
  • YAGNI applies to presumptive features, not to code quality or refactoring
  • Keep options open cheaply: clean code, tests, clear boundaries
  • Irreversible decisions (public APIs, data formats, security) deserve more upfront thought
  • Most predicted requirements change or never arrive

⏱ 30-second answer

YAGNI (You Aren't Gonna Need It, from Extreme Programming) says do not build a capability until you actually need it. Speculative features cost time to build, delay what is needed now, add code to maintain, and are often wrong when the real requirement arrives. YAGNI does not mean skipping tests or refactoring; it is those practices that make adding the feature later cheap. I make an exception for decisions that are expensive to reverse, such as public API contracts, persisted data formats and security, where some upfront design pays off.

🗣 Full answer (senior)

How I would answer this in an interview

"YAGNI ('You Aren't Gonna Need It') comes from Extreme Programming: don't build a capability until you actually need it. Martin Fowler's breakdown of why is the clearest. A presumptive feature has a cost of build (time spent now), a cost of delay (the feature customers need now ships later), a cost of carry (extra code and concepts everyone works around), and often a cost of repair, because when the real requirement arrives it's usually different from the guess. Two clarifications make YAGNI work rather than becoming an excuse. First, it applies to presumptive features and generality, never to tests, refactoring and clean modular code: those are exactly what make adding the feature later cheap. Second, I scale it by reversibility. Internal class design is cheap to change, so I apply YAGNI aggressively. Public API contracts, event schemas, persisted data formats, security and privacy decisions are expensive to reverse once others depend on them, so they get deliberate upfront design and versioning from day one."


1. The four costs of a presumptive feature

Cost Meaning Example
Build Effort spent now 6 weeks on a theming engine
Delay Value lost on what's needed now The reporting feature slips a quarter
Carry Ongoing complexity tax Every change works around tenant-theme hooks
Repair Rework when reality differs Customer two needs SSO/white-label domains, not themes

Even if the guess is right, you pay build + delay + carry earlier than necessary.

2. What YAGNI applies to (and doesn't)

Applies to Does not apply to
Features nobody has asked for ("we might need multi-currency") Tests
Extension points/plug-in frameworks for one variant Refactoring and clean naming
Generic base classes for one use Clear module boundaries
Configuration options nobody sets Known, scheduled requirements
Parameters "in case" Security, privacy and compliance basics
Premature scaling infrastructure Observability needed to operate today

3. Scale it by reversibility

Decision Reversibility Approach
Internal class/module design Easy YAGNI strongly; refactor when needed
Feature flags, configuration Easy Add when a real variant exists
Database schema for new data Medium (migrations help; data backfills don't come free) Model what you know; plan migrations
Public HTTP API shape Hard Design carefully; version from day one
Event/message schemas Hard (consumers you don't control) Make additive change possible; version
Persisted file/data formats Hard Design upfront
Data retention, privacy, security model Very hard Decide upfront
Choice of primary database/cloud Hard Decide deliberately; keep boundaries

4. Keeping options open cheaply

Cheap option Why it helps later
Small, well-named modules A new variant has an obvious home
Tests around behaviour Refactoring to add an extension point is safe
DI and configuration Behaviour can be swapped without rewrites
Ports at volatile boundaries (vendors, I/O) Integrations can change
Additive-change-friendly contracts New fields don't break consumers

Production example

Our [SaaS product] had one customer when the team spent [six weeks] building a multi-tenant theming engine "for when we have more tenants". The reporting feature that customer was actually asking for slipped [a quarter]. When customer two signed [about a year] later, their needs were white-label domains and SSO, not themes; the engine didn't fit and was eventually deleted. Since then I push for building only against a real customer need or ticket, while keeping the code modular and tested so that adding a genuine variant later takes days, not weeks. The exception I argue for is anything that's hard to reverse: on the same product we did invest upfront in a versioned public API and in a tenant-aware data model, because changing those later would have meant migrations across every customer.

🔍 What the interviewer is checking

Checks judgement about speculative work.

Green flags

  • Explains the costs of presumptive features.
  • Separates YAGNI from skipping quality practices.
  • Treats irreversible decisions differently.
  • Shares a real example of speculative work wasted.

Red flags

  • "We always build it flexible in case."
  • "YAGNI means no design."

📚 Learn in depth

1. Origin

YAGNI emerged from Extreme Programming (Kent Beck, Ron Jeffries, late 1990s). Jeffries' version: "Always implement things when you actually need them, never when you just foresee that you need them." Martin Fowler's Yagni article (2015) added the cost-of-build/delay/carry/repair analysis and stressed that YAGNI depends on refactoring, tests and continuous integration.

2. Why predictions fail

  • Requirements are discovered through use; customers react to working software.
  • The further ahead the prediction, the more assumptions it rests on.
  • Even correct predictions often arrive with different details (scale, rules, UX).

3. YAGNI and OCP: reconciling them

Principle Says Reconciliation
OCP Design so variants can be added without modification Add the extension point when the second real variant arrives
YAGNI Don't add extension points speculatively Keep code clean so that refactoring to the extension point is cheap

The combination: first variant as a plain class; second variant triggers a quick, safe refactor to an interface/strategy.

4. A practical checklist before building something "for the future"

Question If no
Is there a customer need, ticket or committed roadmap item? Don't build it
Would retrofitting it later be disproportionately expensive? Don't build it now
Is it a contract or format others will depend on? Lighter design is fine
Would building it now block or delay current priorities? (If yes, definitely don't)

5. Designing irreversible things well (without over-building)

  • APIs: version strategy, consistent error format, pagination, idempotency keys from day one, but only the endpoints needed now.
  • Events: include an id, type, version and timestamp; design for additive change; publish only events consumers need.
  • Data: model known entities properly; avoid "generic attribute bag" schemas "for flexibility".
  • Security: authentication/authorisation model, audit and data classification upfront.

6. Signs of YAGNI violations in a codebase

Sign Example
Interfaces with one implementation and no test reason IReportExporter with only CsvReportExporter
Configuration nobody sets EnableLegacyMode defaulted everywhere
Unused parameters or overloads Export(report, format: "csv", options: null)
Generic engines Rule engine with one hard-coded rule
Infrastructure ahead of load Kafka + sharding for 50 requests/minute

7. Try it yourself

  1. List interfaces in your project with exactly one implementation; classify each: test seam, real boundary, or guessed future.
  2. Inline one guessed abstraction; note whether anything got harder.
  3. Search for configuration options that are never set to non-default values.
  4. For your next feature, write down which parts are hard to reverse and give only those extra design time.
  5. Revisit a past "future-proof" feature and estimate its build/delay/carry/repair costs.

💻 Code

// Speculative: a pluggable export framework for one format.
// public interface IExportFormatter { string Format { get; } byte[] Export(Report r); }
// public sealed class ExportFormatterRegistry { /* reflection-based plug-in loading */ }

// YAGNI: the one format we need, written cleanly so a second is easy later.
using System.Globalization;
using System.Text;

var report = new Report("October sales", [new("Sydney", 120_500m), new("Perth", 64_250m)]);
Console.WriteLine(CsvExport.Export(report));

public sealed record ReportRow(string Region, decimal Revenue);
public sealed record Report(string Title, IReadOnlyList<ReportRow> Rows);

public static class CsvExport
{
    public static string Export(Report report)
    {
        var sb = new StringBuilder("Region,Revenue\n");
        foreach (var row in report.Rows)
            sb.Append(row.Region).Append(',')
              .Append(row.Revenue.ToString(CultureInfo.InvariantCulture)).Append('\n');
        return sb.ToString();
    }
}

If an Excel export is requested, extracting an interface from CsvExport takes minutes because the code is small and tested.

⚠ Common mistakes

1. Building for customers you don't have

What happens: multi-tenant, multi-currency, multi-region features for a single customer. Why it is wrong: real needs differ; work is discarded or reworked. Do instead: build for today's customer; keep code easy to change.

2. Using YAGNI to skip tests or refactoring

What happens: "we don't need tests yet". Why it is wrong: those practices are what make deferring features safe. Do instead: keep quality high so later additions are cheap.

3. Applying YAGNI to irreversible decisions

What happens: a public API or event schema designed ad hoc with no versioning. Why it is wrong: changing it later breaks consumers you don't control. Do instead: invest design time where reversal is expensive.

4. Parameters and options "in case"

What happens: unused overloads, flags and settings. Why it is wrong: they must be understood, tested and preserved. Do instead: add them when a caller needs them.

5. Speculative infrastructure

What happens: message brokers, sharding, microservices ahead of real load. Why it is wrong: operational cost and complexity now for a maybe-later need. Do instead: scale when measurements show the need; keep boundaries clean.

6. "Generic" data models for flexibility

What happens: entity-attribute-value tables to avoid future migrations. Why it is wrong: queries, validation and performance suffer immediately. Do instead: model known data properly and use migrations.

7. Confusing YAGNI with "no design"

What happens: no thought about structure at all. Why it is wrong: change becomes expensive, which defeats YAGNI's premise. Do instead: design for today's needs, cleanly.

8. Ignoring known, committed requirements

What happens: a feature on next sprint's plan is ignored "because YAGNI". Why it is wrong: known needs aren't speculative. Do instead: plan for committed requirements.

9. Never deleting speculative code

What happens: unused frameworks linger "in case". Why it is wrong: carry costs continue forever. Do instead: delete it; version control remembers.

10. Not making the trade-off explicit

What happens: arguments about future-proofing are opinion against opinion. Why it is wrong: decisions become political. Do instead: discuss build/delay/carry/repair costs and reversibility explicitly.

🏛 Senior insight: trade-offs

Seniors use YAGNI selectively: aggressively for internal code, carefully for contracts. The skill is classifying decisions by reversibility and keeping the codebase cheap to change.

Decision type My default
Internal class or module shape Build only what is needed
Feature flags and configuration Add when a real variant exists
Public HTTP API shape Design carefully, add versioning
Event schemas Make additive changes possible from day one
Security and compliance Decide upfront

🔗 Follow-ups the interviewer may ask

  1. When: Candidate reconciles YAGNI with extension points Why: Probes the OCP trade-off
    How to answer

    The Open/Closed Principle says modules should be open for extension but closed for modification: you add new behaviour by adding code, not by editing tested code. In C# this usually means a strategy interface with one implementation per variant, registered in DI, so adding a new payment method is a new class rather than another case in a growing switch. I apply it where change actually happens repeatedly. A small, stable switch (or a switch expression over a closed set) is fine; building extension points for variants that never arrive is speculative generality.

  2. When: Candidate argues for the simplest design Why: Checks KISS reasoning
    How to answer

    KISS (Keep It Simple) means choosing the simplest design that meets the actual requirements, so the code is easy to read, test and change. The useful distinction is between essential complexity (the problem really is hard, such as tax rules) and accidental complexity (layers, patterns and frameworks we added). I also separate simple (few interleaved concepts) from easy (familiar or quick to write): copying a framework template is easy but may not be simple. In practice it means a plain class before an abstraction, a direct EF Core query before a generic repository, and readable code over clever LINQ.

  3. When: Candidate mentions the rule of three Why: Links to DRY and abstraction timing
    How to answer

    DRY (from The Pragmatic Programmer) says every piece of knowledge should have one authoritative representation in the system. It is about rules, not text: the GST rate, the definition of an overdue invoice or a validation rule should live in one place. Two blocks of code that look the same but change for different reasons are accidental duplication, and merging them couples unrelated features. I remove true duplication of business knowledge quickly, and I tolerate similar-looking code until a real shared concept appears, usually around the third repetition.

🛠 Real-world problems: how you handle them

Theming engine nobody used
Situation
A [SaaS product] with one customer built a multi-tenant theming engine.
Signals
[Six] weeks spent; the requested reporting feature slipped a quarter.
Investigation
When customer two arrived, their needs were white-label domains and SSO, not themes.
Resolution
Deleted the theming engine; built domains and SSO from the real requirements.
Prevention
Features need a customer or ticket before work starts; architecture reviews flag speculative generality.

How to tell it (STAR)

We spent [six] weeks on a theming engine for customers we did not have yet. When the second customer arrived they needed something different, and we deleted it. Since then I push for building only for a real ticket, while keeping the code clean enough that adding a variant later is cheap.

↩ Usually asked after

More in Additional Principles

Last updated 31 Oct 2026.

🌐 Sangam's Webring

⏪ Previous 🏠 Main Next ⏩
💬 Chat with me