The Runtime Behind the Prompt

When fzf feels instantaneous, several different kinds of work are happening at once: input is being read, candidate records are being stored, a query is being matched, results are being ordered, and the terminal is being redrawn. This chapter focuses on how those stages cooperate and how fzf's key and event bindings turn them into state changes. The practical payoff is a better way to design and diagnose workflows: you can tell whether a problem is a data update, a search request, an action, or merely a repaint, and choose synchronization or background execution accordingly.

A result is a coordinated snapshot

The visible list is not produced by one monolithic loop. fzf's runtime separates the path from input arrival to screen output into stages with different responsibilities. A reader consumes standard input, a configured command, or the built-in walker and pushes records into chunk storage. The core coordinator takes a snapshot of those chunks and asks the matcher to apply the current query. The matcher scans the snapshot, creates partial results, orders them when sorting is enabled, and hands them to a merger. The terminal coordinator then adopts the result and redraws the interface.

The word “snapshot” matters. While fzf is open, the source may still be emitting records. The matcher must be able to work on a coherent view while the reader continues to ingest new data. A new batch of input can produce a new snapshot and a new search request, but it does not mutate the meaning of the snapshot that a worker is already scanning. This is the basic reason fzf can show a useful list before a long input stream has finished: the user is looking at the best result available for a current view of the data, not waiting for every possible record before seeing anything.

The reader signals two different facts. A read-new event means that the candidate set has changed and the current view may need to be searched again. A read-finished event means that the source has ended, so the current result can be marked final. Those are not just progress notifications. They determine whether a later action should operate on an intermediate view or on a complete input. Options such as --sync can defer the interactive interface until the initial input and search are complete, while ordinary interactive operation can begin earlier. The choice is about when a snapshot becomes useful, not about whether matching exists.

The matcher adds parallelism without giving up a single visible ordering. It divides the available chunks among worker goroutines. Each worker applies the pattern to its assigned chunks and collects partial matches. When sorting is requested and the pattern supports ranking, each worker can sort its local results before a merger combines them into a global view. If sorting is disabled, input order remains the important relation. The merger therefore converts several local computations into one ordered list that the terminal can navigate.

This design makes the cost of a search more predictable in two ways. First, work can use more than one processor when the candidate set is large enough to justify it. Second, the matcher reuses working memory and cached results instead of rebuilding every intermediate object from scratch. The cache is not a promise that every query is free. It is valid only while the input, search mode, and field interpretation remain compatible.