The first version of Fernnode used a polling loop — every 500ms, walk the directory tree, compare mtimes, note changes. This works, but on a project with 8,000 files it used 15% CPU continuously and drained my laptop battery in four hours while I was working. Not acceptable.

How FSEvents works

On macOS, FSEvents is a kernel-level mechanism that delivers change notifications via a file descriptor. The kernel buffers events and coalesces rapid changes to the same path, so you don't get a flood of events when a build tool writes many files in quick succession. Subscribing to a large directory tree costs essentially nothing when nothing is changing — the kernel only wakes your process when there's something to report.

The Go binding I'm using (github.com/fsnotify/fsnotify) wraps FSEvents on macOS and inotify on Linux behind a common interface. On Windows it uses ReadDirectoryChangesW. Same code, platform-appropriate implementation.

The debounce layer

Even with FSEvents, rapid file writes generate many events in quick succession. A text editor saving a file might generate 5–10 events as it writes atomically (write to temp file, rename). Without debouncing, Fernnode would start a sync after each event.

Fernnode debounces with a 150ms quiet window — it waits until there have been no new events for 150ms before starting a sync. The window is configurable via --debounce. For build tools that generate a lot of output files, a longer debounce (500ms–1s) reduces redundant syncs.