Structural — More patterns to know#
Facade#
Provide a clean API over complex subsystems (e.g., PaymentsFacade wrapping gateways).
Proxy#
Lazy-load or guard access (virtual proxy, caching proxy, security proxy).
Decorator#
Add cross-cutting behavior (logging, metrics, caching) without subclass explosion.
public interface IRepository { Task<Order?> GetAsync(Guid id); }
public class CachingRepo(IRepository inner, IMemoryCache cache) : IRepository
{
    public async Task<Order?> GetAsync(Guid id) =>
        await cache.GetOrCreateAsync(id, _ => inner.GetAsync(id));
}
Flyweight#
Share intrinsic state to reduce allocations (use with care; measure!).
When to pick:
You want a simpler facade, a protective or lazy layer (Proxy),
or to attach behavior orthogonally (Decorator).
Choosing the right pattern (a quick guide)#
When you’re unsure which design patterns in C# to apply, start from the symptom:
- 
Too many if/else or switch on type? → Strategy, State, Template Method. 
- 
Tight coupling between modules? → Mediator, Observer, Facade, Bridge. 
- 
Difficult object creation (many params / variants)? → Builder, Factory Method, Abstract Factory. 
- 
Cross-cutting concerns (logging, caching, retries)? → Decorator, Proxy (+ Polly for resilience). 
- 
Performance/memory pressure? → consider Flyweight, pooling, and measure with dotTrace/PerfView. 
Rule of thumb: prefer composition over inheritance, surface interfaces for testability, and let DI wire concrete choices.
Testing & DI tips (keep patterns practical)#
Design patterns in C# shine when they’re easy to test:
- 
Program to interfaces: every collaborator in your pattern (strategy, handler, proxy) should be an interface to enable mocking. 
- 
Use constructor injection for pattern participants; avoid service locators. 
- 
Prefer stateless services when possible; they’re simpler to reason about and reuse in patterns like Command or Strategy. 
- 
For Singleton, avoid global statics—register as Singleton in the DI container and inject where needed. 
- 
For Observer, keep event handlers small and resilient; if work is heavy or remote, hand off to a queue. 
Next steps for your learning#
C# design patterns provide you with customizable and reusable solutions to common software design problems. The patterns you will work with will be dependent upon your individual needs and programs. Once you learn more about the different design patterns, you can begin implementing them into your programs. There are many more design patterns to learn such as:
- 
Memento 
- 
Prototype 
- 
Singleton design pattern 
- 
Decorator 
- 
Flyweight 
- 
Constructor 
To learn more about fundamentals of C#, check out Educative’s course C# for Programmers: A Practical Guide. In this hands-on course, you’ll start with learning the “Hello World!” in C#, and then move into more advanced concepts like classes, inheritance, interfaces, and delegates. By the end, you’ll feel confident using C# in your next project.
Pitfalls & anti-patterns in C##
- 
Singleton everything: over-using singletons creates hidden dependencies and makes tests brittle. Reach for DI scopes instead. 
- 
Inheritance-first designs: many GoF examples use inheritance; in modern C#, interfaces + composition are usually cleaner. 
- 
Decorator bloat: too many nested decorators can obscure behavior. Prefer explicit composition or a small pipeline with named steps. 
- 
Proxy vs. performance: every indirection adds overhead; measure with BenchmarkDotNet. 
- 
Flyweight misuse: sharing to “save memory” can backfire via boxing, copying structs, or cache misses; profile real workloads. 
- 
Async gotchas: if your pattern participants do I/O, make their interfaces Task-based to avoid sync-over-async deadlocks. 
Happy learning!
Continue learning about system design and C##