{"id":"8036828e-47c5-40f5-9f1d-fb32b035c236","shortId":"AGB48s","kind":"skill","title":"go-functional-options","tagline":"Use when designing a Go constructor or factory function with optional configuration — especially with 3+ optional parameters or extensible APIs. Also use when building a New* function that takes many settings, even if they don't mention \"functional options\" by name. Does not cove","description":"# Functional Options Pattern\n\nFunctional options is a pattern where you declare an opaque `Option` type that records information in an internal struct. The constructor accepts a variadic number of these options and applies them to configure the result.\n\n## When to Use\n\nUse functional options when:\n\n- **3+ optional arguments** on constructors or public APIs\n- **Extensible APIs** that may gain new options over time\n- **Clean caller experience** is important (no need to pass defaults)\n\n## The Pattern\n\n### Core Components\n\n1. **Unexported `options` struct** - holds all configuration\n2. **Exported `Option` interface** - with unexported `apply` method\n3. **Option types** - implement the interface\n4. **`With*` constructors** - create options\n\n### Option Interface\n\n```go\ntype Option interface {\n    apply(*options)\n}\n```\n\nThe unexported `apply` method ensures only options from this package can be used.\n\n## Complete Implementation\n\n```go\npackage db\n\nimport \"go.uber.org/zap\"\n\n// options holds all configuration for opening a connection.\ntype options struct {\n    cache  bool\n    logger *zap.Logger\n}\n\n// Option configures how we open the connection.\ntype Option interface {\n    apply(*options)\n}\n\n// cacheOption implements Option for cache setting (simple type alias).\ntype cacheOption bool\n\nfunc (c cacheOption) apply(opts *options) {\n    opts.cache = bool(c)\n}\n\n// WithCache enables or disables caching.\nfunc WithCache(c bool) Option {\n    return cacheOption(c)\n}\n\n// loggerOption implements Option for logger setting (struct for pointers).\ntype loggerOption struct {\n    Log *zap.Logger\n}\n\nfunc (l loggerOption) apply(opts *options) {\n    opts.logger = l.Log\n}\n\n// WithLogger sets the logger for the connection.\nfunc WithLogger(log *zap.Logger) Option {\n    return loggerOption{Log: log}\n}\n\n// Open creates a connection.\nfunc Open(addr string, opts ...Option) (*Connection, error) {\n    // Start with defaults\n    options := options{\n        cache:  defaultCache,\n        logger: zap.NewNop(),\n    }\n\n    // Apply all provided options\n    for _, o := range opts {\n        o.apply(&options)\n    }\n\n    // Use options.cache and options.logger...\n    return &Connection{}, nil\n}\n```\n\n## Usage Examples\n\n### Without Functional Options (Bad)\n\n```go\n// Caller must always provide all parameters, even defaults\ndb.Open(addr, db.DefaultCache, zap.NewNop())\ndb.Open(addr, db.DefaultCache, log)\ndb.Open(addr, false /* cache */, zap.NewNop())\ndb.Open(addr, false /* cache */, log)\n```\n\n### With Functional Options (Good)\n\n```go\n// Only provide options when needed\ndb.Open(addr)\ndb.Open(addr, db.WithLogger(log))\ndb.Open(addr, db.WithCache(false))\ndb.Open(\n    addr,\n    db.WithCache(false),\n    db.WithLogger(log),\n)\n```\n\n## Comparison: Functional Options vs Config Struct\n\n| Aspect | Functional Options | Config Struct |\n|--------|-------------------|---------------|\n| **Extensibility** | Add new `With*` functions | Add new fields (may break) |\n| **Defaults** | Built into constructor | Zero values or separate defaults |\n| **Caller experience** | Only specify what differs | Must construct entire struct |\n| **Testability** | Options are comparable | Struct comparison |\n| **Complexity** | More boilerplate | Simpler setup |\n\n**Prefer Config Struct when**: Fewer than 3 options, options rarely change, all options usually specified together, or internal APIs only.\n\n> Read [references/OPTIONS-VS-STRUCTS.md](references/OPTIONS-VS-STRUCTS.md) when deciding between functional options and config structs, designing a config struct API with proper defaults, or evaluating the hybrid approach for complex constructors.\n\n## Why Not Closures?\n\nAn alternative implementation uses closures:\n\n```go\n// Closure approach (not recommended)\ntype Option func(*options)\n\nfunc WithCache(c bool) Option {\n    return func(o *options) { o.cache = c }\n}\n```\n\nThe interface approach is preferred because:\n\n1. **Testability** - Options can be compared in tests and mocks\n2. **Debuggability** - Options can implement `fmt.Stringer`\n3. **Flexibility** - Options can implement additional interfaces\n4. **Visibility** - Option types are visible in documentation\n\n## Quick Reference\n\n```go\n// 1. Unexported options struct with defaults\ntype options struct {\n    field1 Type1\n    field2 Type2\n}\n\n// 2. Exported Option interface, unexported method\ntype Option interface {\n    apply(*options)\n}\n\n// 3. Option type + apply + With* constructor\ntype field1Option Type1\n\nfunc (o field1Option) apply(opts *options) { opts.field1 = Type1(o) }\nfunc WithField1(v Type1) Option            { return field1Option(v) }\n\n// 4. Constructor applies options over defaults\nfunc New(required string, opts ...Option) (*Thing, error) {\n    o := options{field1: defaultField1, field2: defaultField2}\n    for _, opt := range opts {\n        opt.apply(&o)\n    }\n    // ...\n}\n```\n\n### Checklist\n\n- [ ] `options` struct is unexported\n- [ ] `Option` interface has unexported `apply` method\n- [ ] Each option has a `With*` constructor\n- [ ] Defaults are set before applying options\n- [ ] Required parameters are separate from `...Option`\n\n## Related Skills\n\n- **Interface design**: See [go-interfaces](../go-interfaces/SKILL.md) when designing the `Option` interface or choosing between interface and closure approaches\n- **Naming conventions**: See [go-naming](../go-naming/SKILL.md) when naming `With*` constructors, option types, or the unexported options struct\n- **Function design**: See [go-functions](../go-functions/SKILL.md) when organizing constructors within a file or formatting variadic signatures\n- **Documentation**: See [go-documentation](../go-documentation/SKILL.md) when documenting `Option` types, `With*` functions, or constructor behavior\n\n### External Resources\n\n- [Self-referential functions and the design of options](https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html) - Rob Pike\n- [Functional options for friendly APIs](https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis) - Dave Cheney","tags":["functional","options","golang","skills","cxuu","agent-skills","ai-agent","ai-assistant","claude","claude-code","codex","cursor"],"capabilities":["skill","source-cxuu","skill-go-functional-options","topic-agent-skills","topic-ai-agent","topic-ai-assistant","topic-claude","topic-claude-code","topic-codex","topic-cursor","topic-golang","topic-llm"],"categories":["golang-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/cxuu/golang-skills/go-functional-options","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add cxuu/golang-skills","source_repo":"https://github.com/cxuu/golang-skills","install_from":"skills.sh"}},"qualityScore":"0.491","qualityRationale":"deterministic score 0.49 from registry signals: · indexed on github topic:agent-skills · 82 github stars · SKILL.md body (6,010 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-02T12:55:18.314Z","embedding":null,"createdAt":"2026-04-18T22:13:14.347Z","updatedAt":"2026-05-02T12:55:18.314Z","lastSeenAt":"2026-05-02T12:55:18.314Z","tsv":"'/2014/01/self-referential-functions-and-design.html)':732 '/2014/10/17/functional-options-for-friendly-apis)':742 '/go-documentation/skill.md':709 '/go-functions/skill.md':693 '/go-interfaces/skill.md':656 '/go-naming/skill.md':675 '/zap':180 '1':125,509,543 '2':132,519,556 '3':19,94,140,434,525,567 '4':146,532,593 'accept':73 'add':389,393 'addit':530 'addr':286,334,338,342,347,362,364,368,372 'alia':216 'also':25 'altern':479 'alway':327 'api':24,101,103,446,463,739 'appli':81,138,157,161,206,223,259,301,565,570,579,595,628,640 'approach':471,485,505,668 'argument':96 'aspect':383 'bad':323 'behavior':718 'boilerpl':425 'bool':193,219,227,237,495 'break':397 'build':28 'built':399 'c':221,228,236,241,494,502 'cach':192,212,233,297,344,349 'cacheopt':208,218,222,240 'caller':112,325,407 'chang':438 'checklist':619 'cheney':744 'choos':663 'clean':111 'closur':477,482,484,667 'commandcenter.blogspot.com':731 'commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html)':730 'compar':420,514 'comparison':377,422 'complet':172 'complex':423,473 'compon':124 'config':381,386,429,457,461 'configur':16,84,131,184,197 'connect':188,202,270,283,290,316 'construct':414 'constructor':10,72,98,148,401,474,572,594,635,679,696,717 'convent':670 'core':123 'cove':48 'creat':149,281 'dave':743 'dave.cheney.net':741 'dave.cheney.net/2014/10/17/functional-options-for-friendly-apis)':740 'db':176 'db.defaultcache':335,339 'db.open':333,337,341,346,361,363,367,371 'db.withcache':369,373 'db.withlogger':365,375 'debugg':520 'decid':452 'declar':59 'default':120,294,332,398,406,466,548,598,636 'defaultcach':298 'defaultfield1':610 'defaultfield2':612 'design':7,459,651,658,688,727 'differ':412 'disabl':232 'document':539,704,708,711 'enabl':230 'ensur':163 'entir':415 'error':291,606 'especi':17 'evalu':468 'even':36,331 'exampl':319 'experi':113,408 'export':133,557 'extens':23,102,388 'extern':719 'factori':12 'fals':343,348,370,374 'fewer':432 'field':395 'field1':552,609 'field1option':574,578,591 'field2':554,611 'file':699 'flexibl':526 'fmt.stringer':524 'format':701 'friend':738 'func':220,234,256,271,284,490,492,498,576,585,599 'function':3,13,31,42,49,52,91,321,352,378,384,392,454,687,692,715,724,735 'gain':106 'go':2,9,153,174,324,355,483,542,654,673,691,707 'go-document':706 'go-funct':690 'go-functional-opt':1 'go-interfac':653 'go-nam':672 'go.uber.org':179 'go.uber.org/zap':178 'good':354 'hold':129,182 'hybrid':470 'implement':143,173,209,243,480,523,529 'import':115,177 'inform':66 'interfac':135,145,152,156,205,504,531,559,564,625,650,655,661,665 'intern':69,445 'l':257 'l.log':263 'log':254,273,278,279,340,350,366,376 'logger':194,246,267,299 'loggeropt':242,252,258,277 'mani':34 'may':105,396 'mention':41 'method':139,162,561,629 'mock':518 'must':326,413 'name':45,669,674,677 'need':117,360 'new':30,107,390,394,600 'nil':317 'number':76 'o':306,499,577,584,607,618 'o.apply':309 'o.cache':501 'opaqu':61 'open':186,200,280,285 'opt':224,260,288,308,580,603,614,616 'opt.apply':617 'option':4,15,20,43,50,53,62,79,92,95,108,127,134,141,150,151,155,158,165,181,190,196,204,207,210,225,238,244,261,275,289,295,296,304,310,322,353,358,379,385,418,435,436,440,455,489,491,496,500,511,521,527,534,545,550,558,563,566,568,581,589,596,604,608,620,624,631,641,647,660,680,685,712,729,736 'options.cache':312 'options.logger':314 'opts.cache':226 'opts.field1':582 'opts.logger':262 'organ':695 'packag':168,175 'paramet':21,330,643 'pass':119 'pattern':51,56,122 'pike':734 'pointer':250 'prefer':428,507 'proper':465 'provid':303,328,357 'public':100 'quick':540 'rang':307,615 'rare':437 'read':448 'recommend':487 'record':65 'refer':541 'references/options-vs-structs.md':449,450 'referenti':723 'relat':648 'requir':601,642 'resourc':720 'result':86 'return':239,276,315,497,590 'rob':733 'see':652,671,689,705 'self':722 'self-referenti':721 'separ':405,645 'set':35,213,247,265,638 'setup':427 'signatur':703 'simpl':214 'simpler':426 'skill':649 'skill-go-functional-options' 'source-cxuu' 'specifi':410,442 'start':292 'string':287,602 'struct':70,128,191,248,253,382,387,416,421,430,458,462,546,551,621,686 'take':33 'test':516 'testabl':417,510 'thing':605 'time':110 'togeth':443 'topic-agent-skills' 'topic-ai-agent' 'topic-ai-assistant' 'topic-claude' 'topic-claude-code' 'topic-codex' 'topic-cursor' 'topic-golang' 'topic-llm' 'type':63,142,154,189,203,215,217,251,488,535,549,562,569,573,681,713 'type1':553,575,583,588 'type2':555 'unexport':126,137,160,544,560,623,627,684 'usag':318 'use':5,26,89,90,171,311,481 'usual':441 'v':587,592 'valu':403 'variad':75,702 'visibl':533,537 'vs':380 'withcach':229,235,493 'withfield1':586 'within':697 'withlogg':264,272 'without':320 'zap.logger':195,255,274 'zap.newnop':300,336,345 'zero':402","prices":[{"id":"98f424a4-0909-4fc5-ad9b-88a5bb5ad9e1","listingId":"8036828e-47c5-40f5-9f1d-fb32b035c236","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"cxuu","category":"golang-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T22:13:14.347Z"}],"sources":[{"listingId":"8036828e-47c5-40f5-9f1d-fb32b035c236","source":"github","sourceId":"cxuu/golang-skills/go-functional-options","sourceUrl":"https://github.com/cxuu/golang-skills/tree/main/skills/go-functional-options","isPrimary":false,"firstSeenAt":"2026-04-18T22:13:14.347Z","lastSeenAt":"2026-05-02T12:55:18.314Z"}],"details":{"listingId":"8036828e-47c5-40f5-9f1d-fb32b035c236","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"cxuu","slug":"go-functional-options","github":{"repo":"cxuu/golang-skills","stars":82,"topics":["agent-skills","ai-agent","ai-assistant","claude","claude-code","codex","cursor","go","golang","llm"],"license":"apache-2.0","html_url":"https://github.com/cxuu/golang-skills","pushed_at":"2026-03-15T19:32:10Z","description":"AI Agent Skills for idiomatic, production-ready Go code, distilled from Google, Uber, Community","skill_md_sha":"784bc44ddf097a7df0174d28003a27931f39bed3","skill_md_path":"skills/go-functional-options/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/cxuu/golang-skills/tree/main/skills/go-functional-options"},"layout":"multi","source":"github","category":"golang-skills","frontmatter":{"name":"go-functional-options","license":"Apache-2.0","description":"Use when designing a Go constructor or factory function with optional configuration — especially with 3+ optional parameters or extensible APIs. Also use when building a New* function that takes many settings, even if they don't mention \"functional options\" by name. Does not cover general function design (see go-functions)."},"skills_sh_url":"https://skills.sh/cxuu/golang-skills/go-functional-options"},"updatedAt":"2026-05-02T12:55:18.314Z"}}