Communicate designs with patterns
After 99 days of building systems and learning design patterns, you've probably realized that knowing a pattern is only half the battle. The other half is convincing your team that it's the right choice. Patterns are powerful precisely because they compress years of accumulated design wisdom into a single name—and that compression is your greatest asset when communicating designs with your colleagues.
When you name a pattern in a pull request, you're not just being clever. You're invoking shared context, reducing cognitive load, and creating a shorthand that lets senior developers immediately understand your architectural decisions without reading twenty paragraphs of justification.
Name the pattern in PR descriptions
The simplest and most effective practice is to name the pattern explicitly in your PR title or description. Instead of writing "refactored the event handling system to reduce coupling," write "introduced Observer pattern for decoupled event handling." That single addition transforms a vague description into a precise architectural statement.
I do this consistently across all teams I've worked with. In our last e-commerce platform refactor, I submitted a PR with the title: "Implement Decorator pattern for dynamic product attribute pricing." Immediately, reviewers knew we weren't adding a simple feature—we were changing how product attributes combine their price calculations. No need to explain polymorphism, composition, or runtime behavior modification. The pattern name carried that weight.
Here's a practical example from a CRM system I worked on:
// Without pattern naming: unclear intent
async function processOrder(order: Order) {
let price = order.basePrice;
if (order.hasVAT) price *= 1.18;
if (order.hasInsurance) price += 50;
if (order.hasShipping) price += calculateShipping(order);
return price;
}
// With Decorator pattern naming: intent is clear
class PriceCalculator {
constructor(private base: PriceComponent) {}
calculate(): number { return this.base.calculate(); }
}
class BasePriceComponent implements PriceComponent {
calculate(): number { return this.order.basePrice; }
}
class VATDecorator extends PriceCalculator {
calculate(): number { return this.base.calculate() * 1.18; }
}
class InsuranceDecorator extends PriceCalculator {
calculate(): number { return this.base.calculate() + 50; }
}When you name the Decorator pattern in your PR, reviewers immediately understand: this isn't business logic changes—this is a structural refactor that lets us compose pricing behavior dynamically. That single name saves ten minutes of explanation.
Draw the key relationships
Names alone aren't enough. You need to show how components interact. The best way is a simple diagram—not a UML masterpiece, but a sketch that shows the essence of your pattern. I sketch these in Excalidraw or draw.io, embed them in PR descriptions, and reference them during code reviews.
For a Mediator pattern in a ride-hailing system I built, I included this conceptual diagram:
| Driver | ↔ | Dispatcher (Mediator) | ↔ | Passenger |
| Reports location | Coordinates matching | Requests ride |
This simple arrow diagram immediately clarifies that Driver and Passenger don't communicate directly—all interaction flows through the Dispatcher. Without the diagram, I'd need paragraphs to explain why we extracted the matching logic into a separate class.
Here's concrete code with annotated relationships:
// The pattern structure becomes visible in code organization
interface Colleague {
notify(message: string): void;
}
class Dispatcher implements Mediator {
private driver: Driver;
private passenger: Passenger;
matchRide(passenger: Passenger, driver: Driver) {
// Mediator controls all interaction
driver.notify(`Pickup at ${passenger.location}`);
passenger.notify(`Driver ${driver.name} assigned`);
}
}
class Driver implements Colleague {
constructor(private dispatcher: Mediator) {}
notify(message: string): void {
console.log(`Driver notified: ${message}`);
}
}
class Passenger implements Colleague {
constructor(private dispatcher: Mediator) {}
notify(message: string): void {
console.log(`Passenger notified: ${message}`);
}
}When a junior developer sees this code with the Mediator pattern explicitly named and drawn, they instantly see: colleagues communicate through the mediator, not directly. The structure teaches the pattern.
Explain why, not just what
This is where most technical communication fails. Developers describe what they did (used Factory pattern) without explaining why it matters. Your PR description must answer: why does this pattern solve our specific problem?
A mediocre PR description: "Refactored authentication to use Strategy pattern."
A strong PR description: "Refactored authentication using Strategy pattern because we need to support OAuth, SAML, and internal password-based auth simultaneously. Previously, these were hardcoded in a massive if-else block. This pattern lets us add new auth strategies without modifying existing code, reducing the blast radius of each new provider integration."
In a SaaS platform I worked on, we migrated from a template engine directly generating HTML to a Template Method pattern. Here's how I explained it:
Why this pattern? Our email templates have 90% identical structure but 10% varying logic (different CTAs for different user tiers). Previously, we copy-pasted template code, leading to inconsistency and maintenance nightmares. Template Method lets us define the structure once in a base class and override only the variable parts in subclasses. This ensures consistent email structure across 12 template types while keeping customization points explicit.
That explanation connects the pattern directly to business problems (copy-paste maintenance, inconsistency). It's not about being clever with patterns—it's about solving real problems.
Here's the code that backs up that explanation:
abstract class EmailTemplate {
// Template Method: defines structure
generate(user: User): string {
return `
<header>${this.generateHeader()}</header>
${this.generateBody(user)}
<footer>${this.generateFooter(user)}</footer>
`;
}
protected abstract generateBody(user: User): string;
protected generateHeader(): string {
return '<h1>Welcome to Our Platform</h1>';
}
protected generateFooter(user: User): string {
return `<p>Questions? Contact ${user.supportEmail}</p>`;
}
}
class FreeUserEmail extends EmailTemplate {
protected generateBody(user: User): string {
return `<p>Check out our premium features!</p>`;
}
}
class PremiumUserEmail extends EmailTemplate {
protected generateBody(user: User): string {
return `<p>Your premium features are active.</p>`;
}
}The code structure itself shows why this matters: we eliminate duplication (header, footer are written once) while accommodating variation (each tier generates different body content).
Shared vocabulary speeds up reviews
This is the compound benefit of naming and explaining patterns. Over time, your team develops shorthand. A senior developer can glance at a PR title mentioning "Observer pattern" and immediately spot whether the implementation maintains proper separation of concerns. A junior developer doesn't need to ask "what's the architecture here?"—they have a word they can search and learn from.
I've seen this play out repeatedly in code review conversations:
Without shared vocabulary:
Reviewer: "Why are we creating new objects for each request instead of reusing them?"
Author: "Because each one needs different configuration..."
Reviewer: "Can't we just pass that in?"
Author: "Well, the problem is complexity..."
With shared vocabulary:
Reviewer: "This looks like Factory pattern. Does the factory encapsulate the complex construction logic?"
Author: "Yes, exactly. The factory handles database connection setup and middleware initialization."
Reviewer: "Good call. Looks solid."
The second conversation takes seconds. Both parties understand the shape of the problem immediately.
In our trading platform at Colombo, we standardized pattern naming in architecture decisions. When someone mentions "Command pattern," everyone knows it means we're decoupling the requester (UI button) from the executor (trade engine). When we say "Builder pattern," we're talking about constructing complex trade objects step-by-step. This vocabulary became our internal architecture language.
Common mistakes
- Naming a pattern without explaining why it solves your problem. Just saying "I used Strategy pattern" is empty. A junior developer still won't understand. Always connect the pattern to the specific pain point it addresses in your codebase.
- Forcing patterns where simpler solutions exist. Not every architectural problem needs a Gang of Four pattern. Sometimes a simple function is better than a whole Visitor pattern implementation. Name patterns when they genuinely solve structural problems, not to sound smart.
- Using vague pattern names. "Observer pattern for event handling" is better, but "Observer pattern for decoupled real-time price updates" is best. The specificity matters. It tells reviewers exactly what you're observing.
- Assuming everyone knows the pattern as well as you do. Even with senior developers, assumptions fail. Link to documentation, show the key classes, include a diagram. Your goal is to make reviews fast for everyone, not to gatekeep knowledge.
- Treating patterns as immutable recipes. The best pattern application often bends the textbook version to fit your constraints. Name the pattern, then explain your variant. "We're using Factory pattern, but instead of factory subclasses, we're using a configuration object because..."
Key takeaways
- Name the pattern explicitly in PR titles and descriptions. This single practice cuts review time dramatically by invoking shared context.
- Draw the key relationships, even with simple diagrams. Visual representation of how components interact clarifies intent faster than code alone.
- Explain why the pattern solves your specific problem. Connect design decisions to business constraints, performance needs, or maintenance challenges in your actual codebase.
- Build shared vocabulary with your team. Over time, pattern names become shorthand for architectural conversations, speeding up all future discussions.
- Balance naming patterns with pragmatism. Use pattern names when they genuinely communicate architectural structure, not to overcomplicate simple solutions.
- Include code examples that show the pattern's structure. Let the code itself teach junior developers what the pattern looks like in practice.






Comments
Loading comments…