diff --git a/pkg/app/app.go b/pkg/app/app.go
index baf3ccb..22ad91f 100644
--- a/pkg/app/app.go
+++ b/pkg/app/app.go
@@ -89,32 +89,10 @@ func (a *App) Run() error {
 		}
 		a.Watcher.Add(p.Path)
 	}
-	go a.watch()
+	go startWatcher(a)
 	return http.Serve(a.Listener, a.Router)
 }
 
-// Watch the library path and update Library with changes.
-func (a *App) watch() {
-	for {
-		e, ok := <-a.Watcher.Events
-		if !ok {
-			return
-		}
-		if e.Op&fsnotify.Create > 0 {
-			// add new files to library
-			a.Library.Add(e.Name)
-		} else if e.Op&(fsnotify.Write|fsnotify.Chmod) > 0 {
-			// writes and chmods should remove old file before adding again
-			a.Library.Remove(e.Name)
-			a.Library.Add(e.Name)
-		} else if e.Op&(fsnotify.Remove|fsnotify.Rename) > 0 {
-			// remove and rename just remove file
-			// fsnotify will signal a Create event with the new file name
-			a.Library.Remove(e.Name)
-		}
-	}
-}
-
 // HTTP handler for /
 func (a *App) indexHandler(w http.ResponseWriter, r *http.Request) {
 	log.Printf("/")
diff --git a/pkg/app/watcher.go b/pkg/app/watcher.go
new file mode 100644
index 0000000..2d7a761
--- /dev/null
+++ b/pkg/app/watcher.go
@@ -0,0 +1,59 @@
+package app
+
+import (
+	"time"
+
+	fs "github.com/fsnotify/fsnotify"
+)
+
+// This is the amount of time to wait after changes before reacting to them.
+// Debounce is done because moving files into the watched directories causes
+// many rapid "Write" events to fire which would cause excessive Remove/Add
+// method calls on the Library. To avoid this we accumulate the changes and
+// only perform them once the events have stopped for this amount of time.
+const debounceTimeout = time.Second * 5
+
+// create, write, and chmod all require an add event
+const addFlags = fs.Create | fs.Write | fs.Chmod
+
+// remove, rename, write, and chmod all require a remove event
+const removeFlags = fs.Remove | fs.Rename | fs.Write | fs.Chmod
+
+// watch library paths and update Library with changes.
+func startWatcher(a *App) {
+	timer := time.NewTimer(debounceTimeout)
+	addEvents := make(map[string]struct{})
+	removeEvents := make(map[string]struct{})
+	for {
+		select {
+		case e := <-a.Watcher.Events:
+			if e.Op&removeFlags > 0 {
+				removeEvents[e.Name] = struct{}{}
+			}
+			if e.Op&addFlags > 0 {
+				addEvents[e.Name] = struct{}{}
+			}
+			// reset timer
+			timer.Reset(debounceTimeout)
+		case <-timer.C:
+			// handle remove events first
+			if len(removeEvents) > 0 {
+				for p := range removeEvents {
+					a.Library.Remove(p)
+				}
+				// clear map
+				removeEvents = make(map[string]struct{})
+			}
+			// then handle add events
+			if len(addEvents) > 0 {
+				for p := range addEvents {
+					a.Library.Add(p)
+				}
+				// clear map
+				addEvents = make(map[string]struct{})
+			}
+			// reset timer
+			timer.Reset(debounceTimeout)
+		}
+	}
+}