fzf `--sync` Option Explained: When to Use Synchronous Search
The --sync flag forces fzf to perform a blocking search that waits for the initial filtering and any bound startup actions to complete before rendering the UI, ensuring deterministic execution for multi-stage filtering workflows.
The junegunn/fzf command-line fuzzy finder operates asynchronously by default, displaying the interface immediately and filtering results as you type. However, certain scripting scenarios require the fzf `--sync option to defer UI activation until specific preprocessing steps or side-effects finish executing.
What Does the fzf --sync Option Do?
By default, fzf initializes the terminal interface immediately and processes input asynchronously. When you enable the --sync flag, fzf switches to synchronous search mode, blocking the UI until two conditions are met:
- The initial filtering of the input list completes.
- Any actions bound to the
start,load,result, orfocusevents finish executing.
According to the source code in src/options.go, the flag is defined as "Synchronous search for multi-staged filtering" at line 60. This mode is particularly relevant when chaining fzf with preprocessing commands or when using the --listen server mode where deterministic state matters.
How Synchronous Search Works Under the Hood
Command-Line Parsing
In src/options.go, the --sync boolean flag is parsed alongside other options. When set, it populates a configuration field that propagates to the terminal runner, signaling that the standard async initialization should be bypassed.
Deferred UI Activation
The core implementation resides in src/terminal.go. Normally, fzf activates the terminal UI on the first available tick. However, when --sync is enabled, the code explicitly defers UI activation:
- The interface is suppressed during the initial filter pass.
- UI rendering occurs only on the second tick, after
src/terminal.golines 1736 and 5947 confirm that the initial filtering and event bindings have settled.
This deferred activation prevents the visual flicker that occurs when large datasets load or when heavy preprocessing delays the initial candidate list.
Event Binding Guarantees
Synchronous mode ensures that side-effects complete before user interaction begins. For example, if you bind an action to the focus event but no items exist to focus, src/terminal.go at line 1874 contains logic to activate the UI regardless, ensuring the --sync contract is honored even in edge cases.
When Should You Use the --sync Flag?
Use the fzf --sync option in the following scenarios:
- Multi-stage filtering workflows – When you need to run preprocessing commands (e.g., fetching data from a remote server, parsing large logs) before the user sees the candidate list.
- Avoiding UI flicker with
--listen– When running fzf as a server (--listen), synchronous mode prevents the interface from appearing before the initial data load completes, creating a cleaner user experience. - Guaranteeing side-effect order – When scripts depend on actions bound to
start,load,result, orfocusevents completing before the UI renders, preventing race conditions in automation workflows.
Avoid using --sync for simple, local file filtering where immediate feedback is preferred, as the blocking behavior adds latency before the interface appears.
Practical Examples of fzf Synchronous Search
The following examples demonstrate patterns found in the fzf test suite (test/test_core.rb lines 1346-2122 and test/test_server.rb line 9):
# Example 1: Preprocessing with start event binding
seq 1000 | fzf --sync \
--bind 'start:execute-silent:echo "preload complete" > /tmp/status' \
--preview 'head -n 5 {}'
# Example 2: Server mode with synchronous initialization
fzf --listen 6266 --sync \
--bind 'start:up,load:up,result:up,focus:change-header:Ready' \
--query "initial"
# Example 3: Multi-select with guaranteed event completion
seq 100 | fzf --multi --sync \
--bind 'start:select-all+last+preview(echo welcome)'
# Example 4: Accept-nth with delimiter and synchronous accept
echo "foo,bar,baz" | fzf -d, --accept-nth 2,2 --sync \
--bind 'start:accept' > selected.txt
Summary
- The
--syncoption enables synchronous search mode in fzf, blocking the UI until initial filtering and event bindings complete. - It is defined in
src/options.goand implemented via deferred UI activation insrc/terminal.go(lines 1736, 5947, 1874). - Use it for multi-stage filtering, server-mode stability (
--listen), and deterministic script execution where side-effects must finish before user interaction. - Avoid it for simple queries where immediate UI feedback provides better user experience.
Frequently Asked Questions
What is the difference between --sync and default async mode?
In default async mode, fzf renders the UI immediately and filters results as you type, which provides instant feedback but may show incomplete lists during heavy preprocessing. The --sync flag blocks the interface until the initial candidate list is fully processed and any bound startup actions finish, ensuring the user sees a complete, stable list from the first frame.
Can I use --sync with --listen server mode?
Yes, and this is a recommended use case. When running fzf as a server with --listen, combining it with --sync prevents the UI from appearing before the initial data load completes. This eliminates flicker and ensures that any start or load event bindings execute before the client interface becomes interactive, as demonstrated in test/test_server.rb.
Does --sync affect performance?
The --sync option does not change the computational complexity of the filtering algorithm itself, but it does delay UI rendering until the initial pass completes. For small datasets, this delay is imperceptible. However, with large input streams or slow preprocessing commands, the interface will remain blank longer, which may feel less responsive compared to the default async mode.
How do event bindings interact with synchronous search?
When --sync is enabled, fzf guarantees that actions bound to the start, load, result, and focus events complete before the UI renders. This is implemented in src/terminal.go where the terminal activation is deferred until the second tick after these events settle. Even if no items exist to trigger a focus event (line 1874), the UI will still activate to honor the synchronous contract.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →