How do I watch a file for changes?
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
Many development workflows depend on reacting to file changes the moment they happen. Hot-reloading a web application, tailing a log file, or triggering a build when source code is saved all require a reliable way to watch the filesystem. The good news is that every major operating system exposes kernel-level file notification APIs, and mature libraries wrap them for Python, Node.js, and other languages.
This article explains the OS-level mechanisms first, then shows practical code for the two most popular cross-platform libraries: Python's watchdog and Node.js's chokidar.
OS-Level File Notification APIs
Linux -- inotify
Linux provides the inotify subsystem, which lets a process subscribe to events (create, modify, delete, move) on specific files or directories. The command-line tool inotifywait makes it easy to experiment:
inotify is efficient because the kernel pushes events to your process rather than forcing it to poll. One limitation is that each watch consumes a file descriptor, so monitoring very large directory trees may require raising fs.inotify.max_user_watches.
macOS -- FSEvents
macOS uses the FSEvents framework, which monitors entire directory trees at once rather than individual files. FSEvents is coarser-grained than inotify: it tells you that something changed inside a directory, and your application must determine what exactly changed. The command-line tool fswatch abstracts over both FSEvents and inotify:
Windows -- ReadDirectoryChangesW
Windows exposes ReadDirectoryChangesW, a Win32 API that delivers change notifications for a directory and optionally its subtree. Most cross-platform libraries wrap this API on Windows behind the same interface used for inotify and FSEvents.
Python -- watchdog
The watchdog library provides a cross-platform Python API that uses the best available backend on each OS (inotify on Linux, FSEvents on macOS, ReadDirectoryChangesW on Windows). Install it with pip install watchdog.
The Observer runs in a background thread, so your main thread remains free for other work. You can register multiple handlers for different directories on the same observer.
Node.js -- chokidar
Node.js ships a built-in fs.watch function, but its behavior varies across platforms and it does not handle edge cases like atomic saves or symlinks well. The chokidar library solves these problems and has become the standard choice. Install it with npm install chokidar.
Chokidar normalizes events across Linux, macOS, and Windows and adds features such as glob-based ignoring, initial scan suppression, and debouncing of rapid consecutive changes.
Polling as a Fallback
When native APIs are unavailable, for example on network-mounted filesystems (NFS, SMB) or inside certain containers, you can fall back to polling. Both watchdog and chokidar support a polling mode:
- watchdog: replace
ObserverwithPollingObserverfromwatchdog.observers.polling. - chokidar: pass
{ usePolling: true, interval: 1000 }in the options.
Polling checks file metadata (modification time, size) at a fixed interval. It consumes more CPU than event-based watching and introduces a detection delay equal to the poll interval.
Common Pitfalls
- Hitting the watch limit on Linux. The default
max_user_watches(often 8192) is too low for large projects. Increase it withecho 65536 | sudo tee /proc/sys/fs/inotify/max_user_watches. - Ignoring editor temp files. Many editors write to a temporary file and rename it over the original (atomic save). This produces a delete + create pair instead of a modify event, which can confuse naive handlers.
- Watching network filesystems with event APIs.
inotifyand FSEvents do not work on NFS or SMB mounts. You must use polling mode on these filesystems. - Forgetting to debounce rapid changes. A single file save can trigger multiple events within milliseconds. Without debouncing, your handler may run expensive operations redundantly.
- Leaving zombie watchers. Failing to call
observer.stop()(Python) orwatcher.close()(Node.js) before exit leaks file descriptors and can exhaust OS resources over time.
Summary
- Every major OS provides a kernel-level file notification API:
inotifyon Linux, FSEvents on macOS, andReadDirectoryChangesWon Windows. - Python's
watchdogand Node.js'schokidarwrap these APIs behind a consistent cross-platform interface. - Use polling mode as a fallback when native events are not available, such as on network-mounted filesystems.
- Always debounce rapid events, handle atomic-save patterns, and clean up watchers on exit.
- Increase the OS watch limit before monitoring large directory trees on Linux.
Related reading
- How do Monitored Training Sessions work?
- How do servers talk to clients?
- How do we deploy a trained tensorflow model on a mobile device?
- How do we install dynamic modules non support officially on Nginx Ingress Controller? terraform, helm chart
- How do you actually apply a trained model?
- How do you actually apply a trained model?
- How do you cleanly list all the containers in a kubernetes pod?
- How do you delete an AWS CloudWatch metric?

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.