fzf — Patterns#
Concurrency patterns#
Worker Pool with Atomic Work-Stealing#
- Usage: Matcher spawns
min(NumCPU, numChunks)goroutines for each search scan. - Example:
src/matcher.go:176—var nextChunk atomic.Int32; each goroutine callsnextChunk.Add(1)to atomically claim the next chunk index. No pre-partitioning; workers self-assign until exhausted. - Assessment: Elegant and highly effective. Eliminates the need for a task queue or channel dispatch. Because chunks are uniform in size, load balancing is nearly perfect. Using an atomic counter instead of a channel avoids the scheduler overhead of channel operations in the hot path. Worth emulating for any parallelized batch-processing workload.
Fan-Out / Fan-In (Partial Results Merger)#
- Usage: The N worker goroutines each send a
partialResultintoresultChan; the coordinator drains it and feeds toNewMerger. - Example:
src/matcher.go:186—go func(idx int, slab *util.Slab) { ... resultChan <- partialResult{idx, matches} }(idx, m.slab[idx]) - Assessment: A textbook fan-out/fan-in — each worker produces a partial sorted list;
Mergerimplements a lazy k-way merge (Get(i)) that interleaves them on demand without materializing the full merged slice. This avoids the O(n log n) cost of a complete merge when only the top-K results are needed for display.
Condition-Variable Event Bus (EventBox)#
- Usage: All inter-goroutine signalling flows through a single shared
EventBoxinsrc/util/eventbox.go. Reader, Matcher, and Terminal post events; the coordinator loop blocks onWait(). - Example:
src/util/eventbox.go:30—func (b *EventBox) Wait(callback func(*Events))— acquires the lock, sleeps onsync.Cond.Wait(), delivers the events map to the callback, then clears and releases. - Assessment: A deliberate departure from idiomatic Go channels. The key insight is event coalescing: the map stores only the latest value per event type, so a burst of
EvtReadNewduring fast ingestion collapses to a single notification. With pure channels you would need to drain or drop extras manually. TheWatch/Unwatchmechanism further suppresses events during phases where they are irrelevant (e.g., suppressEvtReadNewwhile waiting forEvtReadFin). Not idiomatic Go, but correct and well-suited to this problem.
Cooperative Cancellation via AtomicBool#
- Usage: The matcher supports in-flight scan cancellation using a custom
AtomicBoolwrapper. - Example:
src/matcher.go:196—if cancelled.Get() { return }inside each worker goroutine. Cancellation is initiated bycancelled.Set(true), thenwaitGroup.Wait()ensures all workers have exited before proceeding.src/util/atomicbool.gowrapssync/atomic.StoreInt32/LoadInt32. - Assessment: Clean cooperative cancellation without context.Context — intentional for a tool where context propagation would add no value (there is no request hierarchy). The
CancelScan()/ResumeScan()pair onMatcher(src/matcher.go:258) provides a public API for pausing the scan during item mutation (e.g., reload).
Channel-Based Semaphore (Background Process Throttling)#
- Usage: Terminal limits concurrent background processes using a buffered channel as a counting semaphore.
- Example:
src/terminal.go:1149—bgSemaphore: make(chan struct{}, maxBgProcesses). Acquiring:t.bgSemaphore <- struct{}{}. Releasing:<-t.bgSemaphore. Per-action semaphores (bgSemaphores map[action]chan struct{}) provide finer-grained limits. - Assessment: Classic and idiomatic Go pattern. Buffered channel as semaphore is widely used; fzf extends it with per-action granularity, which is a nice refinement for preventing a single action from monopolizing the background process budget.
Graceful Shutdown via Paired Channels#
- Usage: Terminal uses
killChan/killedChanfor a synchronous shutdown handshake. - Example:
src/terminal.go:5412—case t.killChan <- true: <-t.killedChan. The sender blocks until the Terminal’s render loop confirms it has exited by drainingkilledChan. - Assessment: Explicit synchronous handshake pattern. Avoids the “fire and forget” pitfall of just closing a done channel; the caller knows the goroutine has actually exited before proceeding. Used in the
Stop()path.
Goroutines with Closure Captures#
- Usage: 25
go func(...)launch points throughout the codebase (concentrated interminal.go). - Example:
src/terminal.go:5627— preview process goroutines captureversion int64by parameter to avoid data races on the outer variable:go func(version int64) { ... }(version). - Assessment: The explicit parameter capture pattern (
go func(v T) { ... }(v)) is correctly applied in the preview goroutines to avoid the classic loop-variable capture bug. Demonstrates awareness of closure semantics.
No context.Context Usage#
- Usage: Zero
context.Contextreferences across the entire codebase. - Assessment: An intentional omission. fzf’s goroutines are long-lived (one per major component) and communicate via the EventBox rather than request-scoped contexts. Cancellation is handled via the
AtomicBool/CancelScanmechanism andreqQuitevents. Appropriate given the single-binary, single-session nature of the tool.
No errgroup Usage#
- Assessment: The Matcher manages its own WaitGroup directly (
src/matcher.go:179—waitGroup := sync.WaitGroup{}).errgroupis not used because errors from match workers are not propagated; a failed match simply returns zero results. The simpler explicit WaitGroup is sufficient.
Error handling#
- Style: Mixed:
errors.Newwith string concatenation (dominant),fmt.Errorf %wwrapping (pprof paths), and exit codes (the primary non-error signal mechanism). - Error types defined: None. There are no custom error struct types in the codebase. All errors are plain strings.
- Wrapping approach:
fmt.Errorf("could not create CPU profile: %w", err)appears insrc/options_pprof.gofor the profiling paths. The rest of the codebase uses bareerrors.Newwith concatenated context strings. - Exit codes as the primary error signal: fzf uses an integer exit code scheme (
ExitOk,ExitError,ExitInterrupt, etc.) for most operational outcomes. The coordinator loop receivesEvtQuit{code: int}and propagates this toos.Exit(). This is the correct model for a CLI tool where the “error” is meaningful only to the calling shell script, not to Go callers. - Examples:
src/options.go:816—return 0, errors.New("not a valid integer: " + str)(option parsing)src/history.go:22—return errors.New("permission denied: " + path)(filesystem error with context added by hand)src/options_pprof.go:19—return fmt.Errorf("could not create CPU profile: %w", err)(wrapping for context, Go 1.13+)
Configuration pattern#
- Approach: Single flat
Optionsstruct populated by a bespokeParseOptions()function (src/options.go). No functional options, no builder, no Viper. - Example:
opts := fzf.ParseOptions(true, os.Args[1:]) fzf.Run(opts)ParseOptions()reads CLI flags,FZF_DEFAULT_OPTS_FILE, andFZF_DEFAULT_OPTSin that order, merging into a single*Options.postProcessOptions()validates and resolves conflicts. - Assessment: The custom flag parser is non-standard (not
flag, notcobra) because fzf supports unusual flag syntax (--no-opt,+1/-1for booleans). This is a pragmatic decision that trades off discoverability for syntax compatibility with the existing user base. TheOptionsstruct approach is perfectly appropriate for a CLI tool with a known, fixed configuration surface.
Dependency injection#
- Approach: Manual wiring in
Run()(src/core.go). Each component receives its dependencies (sharedEventBox, channels, callbacks) as constructor arguments. - Evidence:Components do not look up dependencies; they receive them. No service locator, no DI container.
// src/core.go eventBox := util.NewEventBox() cache := NewChunkCache() chunkList := NewChunkList(cache, pusherCallback) executor := util.NewExecutor(opts.Shell) terminal := NewTerminal(opts, eventBox, executor) reader := NewReader(terminal.pusher, eventBox, executor, ...) matcher := NewMatcher(cache, patternBuilder, eventBox, ...) - Assessment: Textbook manual dependency injection. Small enough that a container would be overhead. The wiring is entirely in one function (
Run), making the dependency graph easy to understand at a glance. This is the right choice at this scale.
Other notable patterns#
Function Types as First-Class Values#
- Usage: Function types are used as dependencies and callbacks throughout.
- Examples:
type Algo func(...) (Result, *[]int)(src/algo/algo.go:320) — the fuzzy match algorithm is a function value, allowing V1/V2 to be swapped at runtime via--algoflag without an interface.type ItemBuilder func(*Item, []byte) bool(src/chunklist.go:12) — item construction callback, allowing different parsing strategies (ANSI, nth-field, raw) to be injected as a closure.type labelPrinter func(tui.Window, int)(src/terminal.go:207) — rendering callback injected into the UI component.- Closure-based
ansiProcessorandtransformIteminsrc/core.go— complex logic encapsulated as closures and passed toNewChunkList.
Custom Slab Allocator (Arena-Style Memory Reuse)#
- Usage:
src/util/slab.go—Slabholds pre-allocated[]int16and[]int32backing arrays. Each Matcher worker thread gets its ownSlaband reuses it across chunk scans. - Assessment: A performance-critical pattern. The fuzzy algorithm (
FuzzyMatchV2) needs large temporary arrays for dynamic programming tables. By reusing a per-goroutine slab rather than allocating per call, fzf avoids GC pressure in the hot search path. This is a significant optimization — essentially a manual arena allocator.
Build Tags for Platform and Feature Isolation#
- Usage: 10 distinct build tag guards across the codebase.
- Examples:
//go:build !windows///go:build windows— platform-specific terminal and proxy code.//go:build tcell || windows—TcellRendereris the default on Windows (where Light is unsupported), opt-in elsewhere.//go:build !386 && !amd64 && !arm64///go:build 386 || amd64 || arm64— SIMD-optimizedIndexByte2vs. generic fallback.//go:build pprof///go:build !pprof— profiling support gated behind a build tag to avoid runtime overhead in normal builds.
- Assessment: Systematic use of build tags for both platform support and feature gating. The SIMD/fallback split is particularly clean — the generic path lives in
result_others.goand the optimized path inresult_x86.go, each with mutually exclusive tags.
Assembly for SIMD Acceleration#
- Usage:
src/algo/indexbyte2_amd64.s,src/algo/indexbyte2_arm64.s— assembly implementations ofIndexByte2, which finds two bytes simultaneously using SIMD instructions. - Assessment: A rare pattern in Go projects. Applied surgically to a single hot function in the fuzzy match algorithm. The Go file
indexbyte2_amd64.goprovides the Go declaration; the.sfile provides the implementation. Demonstrates willingness to drop below the Go abstraction level for measurable performance gains.
sort.Interface Implementations#
- Usage: Three sort adapter types in
src/result.go:ByOrder,ByRelevance,ByRelevanceTac. - Assessment: Standard Go sorting idiom. Used alongside the custom
radixSortResults(src/result.go:351) for the hot path — radix sort is used within each worker (linear time for fixed-width score keys), whilesort.Interfaceimplementations are available for general fallback. The combination is sophisticated and appropriate.
//go:embed for Shell Integration Scripts#
- Usage:
main.goembeds bash, zsh, and fish key-binding scripts at compile time using//go:embed shell/key-bindings.bashetc. - Assessment: Clean use of the embed directive to bundle shell integration scripts into the binary, eliminating the need for a separate installation step.
No Generics#
- Assessment: Despite targeting Go 1.23 (
go 1.23.0ingo.mod), fzf uses no generic types or functions. The codebase is old enough in design that generics were not available when core patterns were established. The function-type approach (type Algo func(...)) achieves similar polymorphism for the algorithm-swap case without generics. Not a weakness — the design does not require generics.
Table-Driven Tests#
- Prevalence: Moderate — used consistently in test files.
- Examples:
src/terminal_test.go:266—tests := map[string]string{...}for key binding name normalization.src/options_test.go:519—testCases := []struct{...}for option parsing.src/algo/indexbyte2_test.go:9—tests := []struct{...}for SIMD path verification.
- Assessment: Standard Go practice. Used where appropriate; not forced everywhere.
Registry Pattern (Action Map)#
- Usage: Terminal maintains a map from key event types to
actionslices (the key binding registry). The HTTP server uses the same action type, making the action language the universal control surface. - Assessment: The action registry is the extensibility point of fzf. The architectural decision to expose this over HTTP (
--listen) transforms fzf into a programmable UI component. The registry pattern enables this without requiring a plugin system.