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:176var nextChunk atomic.Int32; each goroutine calls nextChunk.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 partialResult into resultChan; the coordinator drains it and feeds to NewMerger.
  • Example: src/matcher.go:186go 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; Merger implements 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 EventBox in src/util/eventbox.go. Reader, Matcher, and Terminal post events; the coordinator loop blocks on Wait().
  • Example: src/util/eventbox.go:30func (b *EventBox) Wait(callback func(*Events)) — acquires the lock, sleeps on sync.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 EvtReadNew during fast ingestion collapses to a single notification. With pure channels you would need to drain or drop extras manually. The Watch/Unwatch mechanism further suppresses events during phases where they are irrelevant (e.g., suppress EvtReadNew while waiting for EvtReadFin). 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 AtomicBool wrapper.
  • Example: src/matcher.go:196if cancelled.Get() { return } inside each worker goroutine. Cancellation is initiated by cancelled.Set(true), then waitGroup.Wait() ensures all workers have exited before proceeding. src/util/atomicbool.go wraps sync/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 on Matcher (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:1149bgSemaphore: 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/killedChan for a synchronous shutdown handshake.
  • Example: src/terminal.go:5412case t.killChan <- true: <-t.killedChan. The sender blocks until the Terminal’s render loop confirms it has exited by draining killedChan.
  • 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 in terminal.go).
  • Example: src/terminal.go:5627 — preview process goroutines capture version int64 by 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.Context references 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/CancelScan mechanism and reqQuit events. 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:179waitGroup := sync.WaitGroup{}). errgroup is 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.New with string concatenation (dominant), fmt.Errorf %w wrapping (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 in src/options_pprof.go for the profiling paths. The rest of the codebase uses bare errors.New with 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 receives EvtQuit{code: int} and propagates this to os.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:816return 0, errors.New("not a valid integer: " + str) (option parsing)
    • src/history.go:22return errors.New("permission denied: " + path) (filesystem error with context added by hand)
    • src/options_pprof.go:19return fmt.Errorf("could not create CPU profile: %w", err) (wrapping for context, Go 1.13+)

Configuration pattern#

  • Approach: Single flat Options struct populated by a bespoke ParseOptions() 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, and FZF_DEFAULT_OPTS in that order, merging into a single *Options. postProcessOptions() validates and resolves conflicts.
  • Assessment: The custom flag parser is non-standard (not flag, not cobra) because fzf supports unusual flag syntax (--no-opt, +1/-1 for booleans). This is a pragmatic decision that trades off discoverability for syntax compatibility with the existing user base. The Options struct 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 (shared EventBox, channels, callbacks) as constructor arguments.
  • Evidence:
    // 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, ...)
    Components do not look up dependencies; they receive them. No service locator, no DI container.
  • 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 --algo flag 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 ansiProcessor and transformItem in src/core.go — complex logic encapsulated as closures and passed to NewChunkList.

Custom Slab Allocator (Arena-Style Memory Reuse)#

  • Usage: src/util/slab.goSlab holds pre-allocated []int16 and []int32 backing arrays. Each Matcher worker thread gets its own Slab and 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 || windowsTcellRenderer is the default on Windows (where Light is unsupported), opt-in elsewhere.
    • //go:build !386 && !amd64 && !arm64 / //go:build 386 || amd64 || arm64 — SIMD-optimized IndexByte2 vs. 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.go and the optimized path in result_x86.go, each with mutually exclusive tags.

Assembly for SIMD Acceleration#

  • Usage: src/algo/indexbyte2_amd64.s, src/algo/indexbyte2_arm64.s — assembly implementations of IndexByte2, 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.go provides the Go declaration; the .s file 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), while sort.Interface implementations are available for general fallback. The combination is sophisticated and appropriate.

//go:embed for Shell Integration Scripts#

  • Usage: main.go embeds bash, zsh, and fish key-binding scripts at compile time using //go:embed shell/key-bindings.bash etc.
  • 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.0 in go.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:266tests := map[string]string{...} for key binding name normalization.
    • src/options_test.go:519testCases := []struct{...} for option parsing.
    • src/algo/indexbyte2_test.go:9tests := []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 action slices (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.