The moment you find yourself adding the eighth boolean field to a config struct, you've crossed into uncomfortable territory. EnableCompression, SkipValidation, LogQueries, CacheResults – each one adds another line to your YAML, another field to serialize, another if cfg.Field check cluttering your code.
There's an elegant solution hiding in plain sight: bitwise flags. Instead of eight boolean fields consuming eight bytes (plus alignment overhead), you can pack all those options into a single uint8 that fits in one byte.
The Pattern: From Bools to Bits
Here's how the transformation looks. Instead of this bloated approach:
1type Config struct {
2 EnableCompression bool
3 SkipValidation bool
4 LogQueries bool
5 CacheResults bool
6 EnableMetrics bool
7 DebugMode bool
8 AllowRetries bool
9 UseTLS bool
10}
11
12// Usage scattered everywhere
13if cfg.EnableCompression { /* ... */ }
14if cfg.SkipValidation { /* ... */ }
15if cfg.LogQueries { /* ... */ }You define flags as powers of 2 using Go's iota:
1type ConfigFlags uint8
2
3const (
4 EnableCompression ConfigFlags = 1 << iota // 1 (binary: 00000001)
5 SkipValidation // 2 (binary: 00000010)
6 LogQueries // 4 (binary: 00000100)
7 CacheResults // 8 (binary: 00001000)
8 EnableMetrics // 16 (binary: 00010000)Now your config struct has one field instead of eight, and your YAML shrinks from eight lines to one:
1# Instead of:
2enableCompression: true
3skipValidation: false
4logQueries: true
5cacheResults: false
6# ... 4 more lines
7
8# You get:
9flags: 5 # Binary 101: compression + queries enabledThe Core Operations
Bitwise operations become your new boolean logic. The four essential operations are surprisingly intuitive:
1// Check if a flag is set (like: if cfg.LogQueries)
2func (f ConfigFlags) Has(flag ConfigFlags) bool {
3 return f&flag != 0
4}
5
6// Set a flag (like: cfg.LogQueries = true)
7func (f ConfigFlags) Set(flag ConfigFlags) ConfigFlags {
8 return f | flagUsage becomes clean and expressive:
1// Set multiple flags at once
2cfg.Flags = cfg.Flags.Set(EnableCompression | LogQueries | UseTLS)
3
4// Check flags
5if cfg.Flags.Has(EnableCompression) {
6 // Compress the response
7}
8
9// Advanced: check if ALL specified flags are set
10requiredFlags := EnableCompression | UseTLS
11if cfg.Flags.HasAll(requiredFlags) {
12 // Both compression and TLS are enabled
13}| > The beauty of bitwise flags isn't just the memory savings – it's how they make complex flag combinations feel natural. Setting multiple flags becomes `flag1 | flag2 | flag3`, reading like "flag1 OR flag2 OR flag3". |
|---|
Why This Pattern Matters
The benefits compound as your application grows:
Memory Efficiency: Eight booleans typically consume 8+ bytes due to struct padding. A single uint8 uses exactly one byte and can hold up to 8 flags. Scale to uint64 for 64 flags in just 8 bytes.
Serialization Wins: JSON and YAML become dramatically more compact. Network payloads shrink. Database columns consolidate. One integer field replaces multiple boolean columns.
Performance Gains: Checking multiple conditions becomes a single bitwise operation instead of multiple boolean evaluations. Branch prediction improves with fewer conditional jumps.
Configuration Clarity: Instead of hunting through 8 boolean fields in your config files, you have one canonical flags field. Default combinations become obvious: flags: 0 (nothing enabled) vs flags: 255 (everything enabled for uint8).
Implementation Strategy
Start by identifying related boolean flags in your structs – permissions, feature toggles, processing options, or debug settings are prime candidates. Convert them systematically:
1// Helper for parsing config files
2func ParseFlags(flagsInt int) ConfigFlags {
3 return ConfigFlags(flagsInt)
4}
5
6// Helper for generating config files
7func (f ConfigFlags) ToInt() int {
8 return int(f)Key constraints to remember: uint8 maxes out at 8 flags, uint16 at 16, uint32 at 32, and uint64 at 64. Choose your type based on how many flags you realistically need, with room to grow.
When Bitwise Flags Make Sense
This pattern shines when you have related boolean options that often get checked together. It's perfect for:
- HTTP server configuration (compression, logging, metrics, auth)
- File processing options (validation, transformation, caching)
- Database connection settings (pooling, retries, SSL, logging)
- Permission systems (read, write, delete, admin)
Skip bitwise flags for sparse, unrelated booleans or when you need more than 64 options. In those cases, stick with individual fields or use a map[string]bool.
The next time you catch yourself adding yet another boolean to a config struct, consider whether those flags belong together. If they do, bitwise operations offer a cleaner, faster, more maintainable path forward – and your future self will thank you for the more compact configs.
