Design Patterns & LLD — Go-To Mind Map Notes Source: Dive Into Design Patterns (Alexander Shvets / Refactoring.Guru, v2023) Format: recall-first mind map — every node is Q → hook → when → tiny Go skeleton Diagrams: Mermaid (mindmap / flowchart) Language: Go (interfaces + composition; no classical inheritance trees) How to use these notes (recall pattern) flowchart TD C["Central problem: What varies?"] C --> CREATE["CREATE objects"] C --> STRUCTURE["STRUCTURE objects"] C --> BEHAVE["COMMUNICATE / assign duties"] Enter fullscreen mode Exit fullscreen mode Active recall loop (best retention): flowchart LR Q[Trigger Q] --> Say[Say one-liner] Say --> Sketch[Sketch participants] Sketch --> Code[Write Go skeleton] Code --> Check[Check notes] Check --> Drill[Confusion pairs] Enter fullscreen mode Exit fullscreen mode Cover the answer. Read only the Trigger Q. Say the one-liner out loud (Feynman). Sketch the participants from memory. Write 5–10 lines of Go from memory, then check. Drill confusion pairs (Decorator vs Proxy, Strategy vs State, etc.). Atomic card shape (every pattern): | Field | Purpose | |-------|---------| | Trigger Q | When does this fire in an LLD/interview? | | One-liner | Intent in one breath | | Hook | Real-world metaphor to lock memory | | Structure | Participants (Go names) | | Skeleton | Minimal idiomatic Go | | Trap | Common mix-up / anti-pattern | ROOT MAP mindmap root((Design Patterns & LLD)) 0 Mindset 1 OOP Foundations 2 Relations UML ladder 3 Design Principles 4 SOLID 5 Creational how objects are born 6 Structural how objects are wired 7 Behavioral how objects talk / change 8 Decision tree 9 LLD interview playbook Enter fullscreen mode Exit fullscreen mode 0. Mindset Trigger Q: Pattern vs algorithm? Answer: Algorithm = recipe (exact steps). Pattern = blueprint (shape of solution; code differs per app). Why learn: toolkit of proven designs + shared vocabulary ("use a Strategy here"). Levels of reuse (Gamma): flowchart LR Classes["Classes / libraries"] --> Patterns["Design patterns"] Patterns --> Frameworks["Frameworks"] Enter fullscreen mode Exit fullscreen mode Good design aims for: code reuse + extensibility (change is constant) without rigid coupling. 1. OOP Foundations 1.1 Objects & classes Hook: Class = blueprint; object = instance (Oscar the Cat). State = fields; behavior = methods; members = both. 1.2 Four pillars Pillar One-liner Go feel Abstraction Model only what the context needs (flight sim Airplane ≠ booking Airplane) types that omit irrelevant detail Encapsulation Hide internals; expose a small public surface unexported fields + methods Inheritance Build new types on existing ones embedding (limited; prefer interfaces) Polymorphism Same call, different runtime behavior interface satisfaction // Polymorphism via interface — Go's native "program to an interface" type Flyer interface { Fly(origin, dest string, passengers int) error } type Airport struct{} func (a Airport) Depart(f Flyer) error { return f.Fly("DEL", "BLR", 180) // works for Airplane, Helicopter, … } Enter fullscreen mode Exit fullscreen mode 1.3 Relations between objects (strength ladder) flowchart LR Dep[Dependency] --> Assoc[Association] Assoc --> Agg[Aggregation] Agg --> Comp[Composition] Dep -.->|"weak → strong"| Comp Enter fullscreen mode Exit fullscreen mode flowchart TB subgraph Type relations Inh[Inheritance — is-a] Impl[Implementation — can / fulfills contract] end Enter fullscreen mode Exit fullscreen mode Relation Meaning Memory Dependency Uses briefly (param/local); change may break you "borrows" Association Long-lived link (field / always reachable) "knows" Aggregation Whole–part; parts can outlive whole "has (shared)" Composition Whole owns part lifecycle "owns" Inheritance Is-a; reuses interface + impl "is" Implementation Fulfills a contract "can" 2. Design Principles (pre-SOLID) Encapsulate What Varies Q: Where will change hurt most? Do: Pull varying logic into its own method/type so the stable core doesn't thrash. Hook: Ship compartments — a mine floods one bay, not the hull. func (o Order) Total() float64 { sum := 0.0 for _, li := range o.Items { sum += li.Price * float64(li.Qty) } return sum * (1 + TaxRate(o.Country)) // variation isolated } func TaxRate(country string) float64 { switch country { case "US": return 0.07 case "EU": return 0.20 default: return 0 } } Enter fullscreen mode Exit fullscreen mode Program to an Interface, not an Implementation Q: Can I swap the collaborator without editing callers? Do: Depend on the methods you need, not the concrete type. type Employee interface { DoWork() } type Company struct{} func (c Company) RunDay(staff []Employee) { for _, e := range staff { e.DoWork() // not *Designer, *Dev — the interface } } Enter fullscreen mode Exit fullscreen mode Favor Composition Over Inheritance Q: Am I multiplying subclasses across dimensions (engine × cargo × nav)? Do: "Has-a" + delegate. Runtime-swappable behaviors. Trap: Inheritance = one dimension; multi-dimension → combinatorial explosion. type Engine interface{ Torque() float64 } type Navigator interface{ Route(to string) []string } type Vehicle struct { Engine Navigator } func (v Vehicle) Drive(to string) { _ = v.Torque() _ = v.Route(to) } Enter fullscreen mode Exit fullscreen mode 3. SOLID Letter Rule Recall phrase S One reason to change "one job, one class" O Open for extension, closed for modification "add types, don't edit old ones" L Subtypes must be substitutable "don't surprise the caller" I No fat interfaces "don't force unused methods" D High-level depends on abstractions "details plug into policy" SRP // BAD: Employee manages data AND prints timesheets // GOOD: type Employee struct{ ID, Name string } type TimesheetReporter struct{} func (TimesheetReporter) Print(e Employee) { /* format may change alone */ } Enter fullscreen mode Exit fullscreen mode OCP (+ Strategy) type Shipping interface{ Cost(order Order) float64 } type Order struct{ Shipping Shipping } func (o Order) ShippingCost() float64 { return o.Shipping.Cost(o) } // new shipping = new type; Order untouched Enter fullscreen mode Exit fullscreen mode LSP checklist (Go interfaces) Don't strengthen preconditions / weaken postconditions in implementations Don't throw unexpected errors the contract didn't advertise No "is this the concrete type?" branches that break substitution ISP // BAD: CloudProvider with 40 methods // GOOD: split type BlobStore interface{ Put(key string, b []byte) error } type Queue interface{ Publish(topic string, msg []byte) error } Enter fullscreen mode Exit fullscreen mode DIP type ReportStore interface { Load(id string) (Report, error) Save(Report) error } type BudgetReport struct{ Store ReportStore } // high-level depends on interface // PostgresStore / FileStore implement ReportStore — details depend on abstraction Enter fullscreen mode Exit fullscreen mode 5. CREATIONAL — "how are objects born?" mindmap root((Creational)) Factory Method subclass decides product Abstract Factory families of products Builder step-by-step complex object Prototype clone existing instance Singleton one shared instance Enter fullscreen mode Exit fullscreen mode Factory Method AKA: Virtual Constructor Trigger Q: I know the interface of a product, but not which concrete type until a subclass/config decides? One-liner: Creator defines CreateProduct(); subclasses return concrete products. Hook: Logistics app — Transport is Truck or Ship; Logistics.CreateTransport() deferred to RoadLogistics / SeaLogistics. When: framework/library hooks; parallel product hierarchies; replace new Concrete sprinkled everywhere. type Button interface{ Render() } type Dialog interface { CreateButton() Button // factory method Render() } type WindowsButton struct{} func (WindowsButton) Render() { /* native win btn */ } type WindowsDialog struct{} func (WindowsDialog) CreateButton() Button { return WindowsButton{} } func (d WindowsDialog) Render() { btn := d.CreateButton() btn.Render() } Enter fullscreen mode Exit fullscreen mode Trap: Not the same as Abstract Factory (one product vs families). Relates: often grows into Abstract Factory; pairs with Template Method; Iterator may use it for iterators. Abstract Factory Trigger Q: Need families of related products (WinButton+WinCheckbox) and must keep them consistent? One-liner: Interface of factory methods for each product in a family; concrete factories produce one family. Hook: Cross-platform UI kit — GUIFactory → WinFactory / MacFactory. type Button interface{ Paint() } type Checkbox interface{ Paint() } type GUIFactory interface { CreateButton() Button CreateCheckbox() Checkbox } type WinFactory struct{} func (WinFactory) CreateButton() Button { return WinButton{} } func (WinFactory) CreateCheckbox() Checkbox { return WinCheckbox{} } func Application(f GUIFactory) { f.CreateButton().Paint() f.CreateCheckbox().Paint() } Enter fullscreen mode Exit fullscreen mode Trap: Adding a new product type forces changing the factory interface (and all factories). Builder Trigger Q: Constructor has 10+ params / many optional steps / same process, different representations? One-liner: Build step-by-step; director can reuse the recipe; get product at the end. Hook: House builder — walls, doors, roof; same steps → wooden or stone house. type House struct{ Walls, Doors, Roof string } type HouseBuilder interface { BuildWalls() BuildDoors() BuildRoof() GetHouse() House } type WoodBuilder struct{ h House } func (b *WoodBuilder) BuildWalls() { b.h.Walls = "wood" } func (b *WoodBuilder) BuildDoors() { b.h.Doors = "wood" } func (b *WoodBuilder) BuildRoof() { b.h.Roof = "wood" } func (b *WoodBuilder) GetHouse() House { return b.h } type Director struct{} func (Director) Construct(b HouseBuilder) House { b.BuildWalls(); b.BuildDoors(); b.BuildRoof() return b.GetHouse() } Enter fullscreen mode Exit fullscreen mode Go tip: fluent setters (WithX() *Builder) are an idiomatic Builder variant. Trap: Overkill for simple structs — use functional options for light cases. Prototype Trigger Q: Creating from scratch is expensive / I don't want to depend on concrete classes to copy? One-liner: Clone existing objects via a common Clone() contract. Hook: Cell mitosis; shape editor duplicate. type Shape interface { Clone() Shape Draw() } type Circle struct { X, Y, R int Color string } func (c Circle) Clone() Shape { cp := c // shallow copy; deep-copy slices/maps if needed return &cp } func (c Circle) Draw() { /* … */ } Enter fullscreen mode Exit fullscreen mode Trap: Deep vs shallow copy bugs with nested references. Singleton Trigger Q: Exactly one instance + global access (config, logger, connection pool)? One-liner: Ensure one instance; provide a single access point. Hook: Government / one president. package config import "sync" type Config struct{ DSN string } var ( once sync.Once inst *Config ) func Get() *Config { once.Do(func() { inst = &Config{DSN: "postgres://…"} }) return inst } Enter fullscreen mode Exit fullscreen mode Trap: Hidden global state; hard to test — prefer DI; if needed, use sync.Once (thread-safe). Pros: controlled access, lazy init. Cons: violates SRP often; masks dependencies. 6. STRUCTURAL — "how are objects wired?" mindmap root((Structural)) Adapter make incompatible APIs work Bridge split abstraction from implementation Composite tree of part / whole Decorator wrap to add behavior Facade simplify a subsystem Flyweight share intrinsic state RAM Proxy stand-in controlling access Enter fullscreen mode Exit fullscreen mode Adapter AKA: Wrapper Trigger Q: Third-party / legacy API shape ≠ what my code expects? One-liner: Translate one interface into another. Hook: Power plug adapter. type JSONAnalytics interface { AnalyzeJSON(data []byte) (string, error) } // legacy type XMLService struct{} func (XMLService) AnalyzeXML(xml string) string { return "ok" } type XMLToJSONAdapter struct{ Inner XMLService } func (a XMLToJSONAdapter) AnalyzeJSON(data []byte) (string, error) { xml := jsonToXML(data) // conversion return a.Inner.AnalyzeXML(xml), nil } Enter fullscreen mode Exit fullscreen mode Confusion: Adapter changes interface; Decorator keeps interface & adds behavior; Facade simplifies a subsystem. Bridge Trigger Q: Two independent dimensions both need to vary (shape × renderer; remote × device)? One-liner: Split into Abstraction + Implementation hierarchies linked by composition. Hook: Remote control (abstraction) ↔ Device (TV/Radio implementation). type Device interface { IsOn() bool On(); Off() SetVolume(int) } type Remote struct{ Dev Device } func (r *Remote) Toggle() { if r.Dev.IsOn() { r.Dev.Off() } else { r.Dev.On() } } type AdvancedRemote struct{ Remote } func (r *AdvancedRemote) Mute() { r.Dev.SetVolume(0) } Enter fullscreen mode Exit fullscreen mode Trap: Looks like Strategy; Bridge is about structural decoupling of two hierarchies long-term. Composite Trigger Q: Tree of objects; clients should treat leaf and group the same? One-liner: Uniform component interface for leaves and composites. Hook: File system / org chart / nested boxes in a graphics editor. type Graphic interface { Draw() Move(dx, dy int) } type Dot struct{ X, Y int } func (d *Dot) Draw() { /* point */ } func (d *Dot) Move(dx, dy int) { d.X += dx; d.Y += dy } type Compound struct{ Children []Graphic } func (c *Compound) Draw() { for _, ch := range c.Children { ch.Draw() } } func (c *Compound) Move(dx, dy int) { for _, ch := range c.Children { ch.Move(dx, dy) } } Enter fullscreen mode Exit fullscreen mode Decorator AKA: Wrapper Trigger Q: Add responsibilities at runtime without exploding subclasses? One-liner: Stack wrappers that share the component interface. Hook: Wearing clothes — layers. type Notifier interface{ Send(msg string) } type EmailNotifier struct{} func (EmailNotifier) Send(msg string) { /* email */ } type SMSDecorator struct{ Inner Notifier } func (d SMSDecorator) Send(msg string) { d.Inner.Send(msg) /* also SMS */ } type SlackDecorator struct{ Inner Notifier } func (d SlackDecorator) Send(msg string) { d.Inner.Send(msg) /* also Slack */ } // usage: SlackDecorator{SMSDecorator{EmailNotifier{}}} Enter fullscreen mode Exit fullscreen mode Confusion pair: Decorator = add behavior, same interface. Proxy = control access (lazy, auth, remote). Adapter = change interface. Facade Trigger Q: Client talks to a messy subsystem of many classes? One-liner: One simple entry API over a complex subsystem. Hook: Ordering pizza by phone — one number, kitchen chaos hidden. type VideoConverter struct { // holds ffmpeg, codec, bitrate helpers… } func (VideoConverter) Convert(filename, format string) string { // orchestrate: decode → filter → encode → write return "output." + format } Enter fullscreen mode Exit fullscreen mode Trap: Don't let Facade become a god object — keep it a thin orchestrator. Flyweight Trigger Q: Millions of similar objects; RAM blown by duplicated immutable data? One-liner: Share intrinsic (immutable) state; pass extrinsic state in methods. Hook: Forest of trees — shared TreeType (texture/color), many Tree positions. type TreeType struct{ Name, Color, Texture string } // intrinsic, shared type TreeTypeFactory struct{ cache map[string]*TreeType } func (f *TreeTypeFactory) Get(name, color, texture string) *TreeType { key := name + color + texture if t, ok := f.cache[key]; ok { return t } t := &TreeType{name, color, texture} f.cache[key] = t return t } type Tree struct { X, Y int Type *TreeType // flyweight } func (t Tree) Draw() { /* use t.X,t.Y + shared Type */ } Enter fullscreen mode Exit fullscreen mode Proxy Trigger Q: Need lazy load, access control, logging, caching, or remote stub in front of a real object? One-liner: Same interface as the real subject; proxy delegates after extra work. Hook: Credit card as proxy for bank account. type Image interface{ Display() } type RealImage struct{ Path string } func (r *RealImage) Display() { /* heavy load from disk */ } type ImageProxy struct { Path string real *RealImage } func (p *ImageProxy) Display() { if p.real == nil { p.real = &RealImage{Path: p.Path} // lazy } // optional: auth / cache / log p.real.Display() } Enter fullscreen mode Exit fullscreen mode Types: virtual (lazy), protection (ACL), remote, logging/smart reference. 7. BEHAVIORAL — "how do objects talk / change?" mindmap root((Behavioral)) Chain of Responsibility pass request along handlers Command request as object Iterator traverse without exposing structure Mediator hub for colleague communication Memento snapshot / undo Observer pub-sub State behavior by internal state Strategy swap algorithms Template Method algorithm skeleton + hooks Visitor externalize operations on a structure Enter fullscreen mode Exit fullscreen mode Chain of Responsibility AKA: CoR, Chain of Command Trigger Q: Multiple handlers might process a request; avoid hard-coded if/else towers? One-liner: Handlers linked in a chain; each handles or forwards. Hook: Support tiers L1→L2→L3; corporate purchase approvals. type Handler interface { SetNext(Handler) Handler Handle(amount float64) string } type base struct{ next Handler } func (b *base) SetNext(h Handler) Handler { b.next = h; return h } func (b *base) forward(amount float64) string { if b.next != nil { return b.next.Handle(amount) } return "unhandled" } type Manager struct{ base } func (m *Manager) Handle(amount float64) string { if amount Create[CREATE] Start --> Structure[STRUCTURE / wrap] Start --> Behave[BEHAVIOR / communication] Create --> C1[one of several products] --> FM[Factory Method] Create --> C2[family of products] --> AF[Abstract Factory] Create --> C3[complex step build] --> B[Builder] Create --> C4[copy existing] --> P[Prototype] Create --> C5[exactly one] --> S[Singleton — last resort] Structure --> S1[incompatible API] --> Ad[Adapter] Structure --> S2[two varying hierarchies] --> Br[Bridge] Structure --> S3[tree part/whole] --> Co[Composite] Structure --> S4[add behavior same API] --> De[Decorator] Structure --> S5[simplify subsystem] --> Fa[Facade] Structure --> S6[share RAM state] --> Fl[Flyweight] Structure --> S7[control access / lazy / remote] --> Pr[Proxy] Behave --> B1[pass along handlers] --> Ch[Chain of Responsibility] Behave --> B2[undo / queue / log action] --> Cm[Command] Behave --> B3[traverse collection] --> It[Iterator] Behave --> B4[untangle many-to-many talk] --> Me[Mediator] Behave --> B5[snapshot / undo state] --> Mm[Memento] Behave --> B6[notify many listeners] --> Ob[Observer] Behave --> B7[behavior by state machine] --> St[State] Behave --> B8[swap algorithm] --> Sy[Strategy] Behave --> B9[shared algorithm skeleton] --> Tm[Template Method] Behave --> B10[add ops to stable structure] --> Vi[Visitor] Enter fullscreen mode Exit fullscreen mode 9. Confusion pairs (drill these) Pair Difference Adapter vs Decorator vs Proxy change interface / add behavior / control access Adapter vs Facade one object vs whole subsystem simplification Strategy vs State injected algorithm vs self-transitioning states Strategy vs Bridge algorithm swap vs dual hierarchy decoupling Mediator vs Observer central coordinator vs distributed pub-sub Chain vs Decorator stop/forward request vs stack all behaviors Factory Method vs Abstract Factory one product hook vs product families Composite vs Decorator tree of children vs linear wrapper chain Command vs Strategy request object (often undoable) vs replaceable algorithm Template Method vs Strategy inheritance/hooks vs composition/delegate 10. LLD practices checklist (from the book → Go habits) Find what varies → extract interface / strategy / state. Depend on interfaces → accept interface{…} at boundaries. Compose → embed small collaborators; avoid deep type hierarchies. SRP → split God structs. OCP → add types, don't edit battle-tested cores. LSP → implementations must honor the contract. ISP → small interfaces (io.Reader style). DIP → high-level packages define interfaces; infra implements. Name the pattern in design docs — shared language. Don't pattern-hunt — complexity only when change pressure justifies it. 11. Quick Go idiom map (book OOP → Go) Book concept Go idiom Abstract class Interface + optional helper funcs Protected members Same package / unexported Multiple inheritance Interface embedding Polymorphism Implicit interface satisfaction Singleton sync.Once or DI container Observer interfaces, channels, event bus Iterator for range, iter.Seq Decorator/Proxy wrapping structs implementing same interface
What Varies? A Go-To Mind Map of Design Patterns & SOLID
Full Article
Original Source
Read the full article at Dev →KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.