Note. Please download the code zip file from the top of this article.
YAGNI
YAGNI stands for "You Aren't Gonna Need It" — a fundamental principle in software development that encourages developers to implement only the features that are necessary for the current requirements, not the ones you think you might need in the future.
This principle originates from Extreme Programming (XP) and is closely aligned with Agile methodologies. The core idea is simple yet powerful: avoid building features based on speculation about future needs. Instead, focus on what's required right now, and add functionality only when it becomes truly necessary.
From a developer's perspective, adding unnecessary features leads to increased complexity, longer development times, wasted effort, and potentially more bugs. The YAGNI principle helps prevent code bloat and keeps your codebase clean, maintainable, and focused.
Why Developers Should Follow YAGNI
There are several compelling reasons to embrace the YAGNI principle in your development practice:
Cost of Building: Every feature you implement requires time, effort, and resources. Building features you don't currently need is a direct waste of these valuable resources.
Cost of Delay: When you spend time on speculative features, you delay the delivery of features that actually provide value to users right now. This opportunity cost can be significant in competitive markets.
Cost of Carry: Unnecessary code adds complexity to your codebase. This complexity makes it harder to work on other parts of the system, leading to additional time and effort for future development.
Cost of Repair: Code that exists must be maintained. Even if a feature is never used, it can introduce bugs, requires updates when you refactor other parts of the system, and accumulate technical debt over time.
Reduced Maintainability: Every line of code you write is a line you must maintain. Unused features clutter your codebase, making it harder for other developers to understand the system and identify what's actually important.
Improved Focus: YAGNI helps development teams stay focused on delivering actual business value rather than getting distracted by hypothetical future scenarios.
The Relationship Between YAGNI and Other Principles
YAGNI works in harmony with other important software development principles:
KISS (Keep It Simple, Stupid): Both YAGNI and KISS advocate for simplicity in design. While KISS focuses on avoiding unnecessary complexity in how you implement features, YAGNI focuses on avoiding unnecessary features altogether.
DRY (Don't Repeat Yourself): YAGNI complements DRY by ensuring you only write code once for features you actually need, rather than creating multiple implementations for future possibilities.
SOLID Principles: YAGNI can be applied to SOLID principles by letting abstractions emerge naturally rather than creating complex class hierarchies upfront. The key is to refactor toward patterns as needs arise, rather than designing them in from the beginning.
Refactoring: YAGNI depends on your ability to refactor code effectively. The principle assumes that when you do need new functionality, you can refactor your simple code to accommodate it.
Understanding YAGNI Through Examples
Let me illustrate the YAGNI principle through practical examples. Each scenario demonstrates how developers often over-engineer solutions by anticipating future needs.
Example 1: Email Notification Service
Imagine you need to send email notifications to users. Here's how developers often violate YAGNI:
❌ WITHOUT YAGNI - Over-engineered for Future Needs
// Anticipating multiple notification channels we don't use yet
public class NotificationService
{
private IEmailProvider _emailProvider;
private ISmsProvider _smsProvider;
private IPushNotificationProvider _pushProvider;
private INotificationQueue _queue;
private INotificationLogger _logger;
public async Task SendNotificationAsync(
User user,
string message,
NotificationType type = NotificationType.Email,
Priority priority = Priority.Normal,
bool queueIfFailed = true,
int retryCount = 3)
{
INotificationResult result = null;
switch (type)
{
case NotificationType.Email:
result = await _emailProvider.SendAsync(user.Email, message);
break;
case NotificationType.Sms:
result = await _smsProvider.SendAsync(user.Phone, message);
break;
case NotificationType.Push:
result = await _pushProvider.SendAsync(user.DeviceToken, message);
break;
}
if (!result.Success && queueIfFailed)
{
await _queue.EnqueueAsync(user, message, type, retryCount);
}
await _logger.LogNotificationAsync(user.Id, type, result.Success);
}
}
✅ WITH YAGNI - Simple Solution for Current Need
// Only email is needed right now
public class EmailNotificationService
{
private readonly SmtpClient _smtpClient;
public EmailNotificationService(string smtpHost, int smtpPort)
{
_smtpClient = new SmtpClient(smtpHost, smtpPort);
}
public async Task SendEmailAsync(string toEmail, string subject, string body)
{
var message = new MailMessage("[email protected]", toEmail)
{
Subject = subject,
Body = body
};
await _smtpClient.SendMailAsync(message);
}
}
The first implementation includes SMS, push notifications, queueing, retry logic, and extensive logging—none of which are currently needed. The second implementation provides exactly what's required: simple email sending. When SMS or push notifications are actually needed, you can refactor and extend the code at that time.
Example 2: IoT Device Configuration Manager
Consider a system for managing configuration settings for IoT devices:
❌ WITHOUT YAGNI - Multiple Storage Strategies
// Creating abstractions for storage methods we don't use
public interface IConfigurationStrategy
{
void ApplyConfiguration(DeviceConfig config);
}
public class LocalFileConfigStrategy : IConfigurationStrategy
{
public void ApplyConfiguration(DeviceConfig config) { /* ... */ }
}
public class CloudConfigStrategy : IConfigurationStrategy
{
public void ApplyConfiguration(DeviceConfig config) { /* ... */ }
}
public class DatabaseConfigStrategy : IConfigurationStrategy
{
public void ApplyConfiguration(DeviceConfig config) { /* ... */ }
}
public class ConfigurationManager
{
private readonly IConfigurationStrategy _strategy;
private readonly IConfigurationCache _cache;
private readonly IConfigurationValidator _validator;
private readonly IConfigurationLogger _logger;
public ConfigurationManager(
IConfigurationStrategy strategy,
IConfigurationCache cache,
IConfigurationValidator validator,
IConfigurationLogger logger)
{
_strategy = strategy;
_cache = cache;
_validator = validator;
_logger = logger;
}
public async Task<ConfigResult> ApplyConfigurationAsync(
DeviceConfig config,
bool useCache = true,
int retryCount = 3)
{
if (useCache && _cache.HasConfig(config.DeviceId))
{
return _cache.GetConfig(config.DeviceId);
}
if (!_validator.Validate(config))
{
_logger.LogError("Invalid configuration");
return ConfigResult.Invalid;
}
var result = await RetryPolicy.ExecuteAsync(
() => _strategy.ApplyConfiguration(config),
retryCount);
_cache.StoreConfig(config.DeviceId, result);
return result;
}
}
✅ WITH YAGNI - Direct File-Based Implementation
// Simple solution for current file-based requirement
public class DeviceConfigManager
{
public void ApplyConfiguration(string deviceId, Dictionary<string, string> settings)
{
var configFile = $"device_{deviceId}.json";
var json = JsonSerializer.Serialize(settings);
File.WriteAllText(configFile, json);
}
public Dictionary<string, string> GetConfiguration(string deviceId)
{
var configFile = $"device_{deviceId}.json";
if (!File.Exists(configFile))
return new Dictionary<string, string>();
var json = File.ReadAllText(configFile);
return JsonSerializer.Deserialize<Dictionary<string, string>>(json);
}
}
The complex version anticipates cloud storage, database storage, caching, validation, and retry logic—all features that aren't currently needed. The simple version handles the current requirement: storing and retrieving device configurations from local files. If cloud storage becomes necessary later, you can refactor at that time with a better understanding of the actual requirements.
Example 3: Customer Order Processing
Let's examine an e-commerce order processing system:
❌ WITHOUT YAGNI - Multiple Payment Methods
// Building support for payment methods we don't accept
public abstract class PaymentProcessor
{
public abstract Task<PaymentResult> ProcessPaymentAsync(decimal amount);
public abstract Task<RefundResult> RefundAsync(string transactionId);
public abstract Task<bool> ValidatePaymentMethodAsync();
}
public class CreditCardProcessor : PaymentProcessor
{
public override async Task<PaymentResult> ProcessPaymentAsync(decimal amount)
{
// Credit card processing logic
return await Task.FromResult(new PaymentResult { Success = true });
}
public override async Task<RefundResult> RefundAsync(string transactionId)
{
// Refund logic not currently needed
return await Task.FromResult(new RefundResult());
}
public override async Task<bool> ValidatePaymentMethodAsync()
{
return await Task.FromResult(true);
}
}
// Payment methods we might need someday
public class CryptocurrencyProcessor : PaymentProcessor { /* ... */ }
public class BankTransferProcessor : PaymentProcessor { /* ... */ }
public class DigitalWalletProcessor : PaymentProcessor { /* ... */ }
public class OrderProcessor
{
private readonly Dictionary<string, PaymentProcessor> _processors;
public OrderProcessor()
{
_processors = new Dictionary<string, PaymentProcessor>
{
{ "creditcard", new CreditCardProcessor() },
{ "crypto", new CryptocurrencyProcessor() },
{ "bank", new BankTransferProcessor() },
{ "wallet", new DigitalWalletProcessor() }
};
}
public async Task<bool> ProcessOrderAsync(Order order, string paymentType)
{
if (!_processors.ContainsKey(paymentType))
return false;
var processor = _processors[paymentType];
var result = await processor.ProcessPaymentAsync(order.TotalAmount);
return result.Success;
}
}
✅ WITH YAGNI - Credit Card Only
// Current requirement: credit card payments only
public class OrderProcessor
{
public async Task<bool> ProcessOrderAsync(Order order, CreditCardInfo cardInfo)
{
if (string.IsNullOrEmpty(cardInfo.CardNumber) ||
string.IsNullOrEmpty(cardInfo.CVV))
{
return false;
}
var paymentGateway = new PaymentGatewayClient();
var response = await paymentGateway.ChargeCardAsync(
cardInfo.CardNumber,
cardInfo.CVV,
order.TotalAmount);
if (response.IsSuccess)
{
order.Status = OrderStatus.Paid;
order.PaymentDate = DateTime.UtcNow;
order.TransactionId = response.TransactionId;
}
return response.IsSuccess;
}
}
The first implementation builds infrastructure for cryptocurrency, bank transfers, and digital wallets when the business only accepts credit cards. This is speculative engineering that wastes time and adds unnecessary complexity. The simple version handles the actual business requirement efficiently.
Example 4: User Authentication System
Consider implementing user login for a web application:
❌ WITHOUT YAGNI - Complex Multi-Provider Authentication

Naresh Kumar KattaPosted Oct 30, 2025, 2:27 AM
Thanks for reading! If you’re interested, you can check out the code/examples from this post on my GitHub:https://github.com/devnareshkumar/YAGNI-Principle-CSharp-Examples