diff --git a/spec/models/mutable-query-result-set-spec.coffee b/spec/models/mutable-query-result-set-spec.coffee
index 5f5caee8d..254ca1786 100644
--- a/spec/models/mutable-query-result-set-spec.coffee
+++ b/spec/models/mutable-query-result-set-spec.coffee
@@ -70,7 +70,7 @@ describe "MutableQueryResultSet", ->
         'C': {id: 'C', clientId: 'C-local'},
       })
 
-  describe "addIdsInRange", ->
+  fdescribe "addIdsInRange", ->
     describe "when the set is currently empty", ->
       it "should set the result set to the provided one", ->
         @set = new MutableQueryResultSet()
@@ -101,10 +101,13 @@ describe "MutableQueryResultSet", ->
           @set.addIdsInRange(['0', '1', '2'], new QueryRange(offset: 2, limit: 3))
         .not.toThrow()
 
-      it "should throw an exception if the range provided and the ids provided are different lengths", ->
-        expect =>
-          @set.addIdsInRange(['F', 'G', 'H'], new QueryRange(offset: 10, limit: 5))
-        .toThrow()
+      it "should work if the IDs array is shorter than the result range they represent (addition)", ->
+        @set.addIdsInRange(['F', 'G', 'H'], new QueryRange(offset: 10, limit: 5))
+        expect(@set.ids()).toEqual(['A','B','C','D','E', 'F', 'G', 'H'])
+
+      it "should work if the IDs array is shorter than the result range they represent (replacement)", ->
+        @set.addIdsInRange(['A', 'B', 'C'], new QueryRange(offset: 5, limit: 5))
+        expect(@set.ids()).toEqual(['A','B','C'])
 
       it "should correctly add ids (trailing) and update the offset", ->
         @set.addIdsInRange(['F', 'G', 'H'], new QueryRange(offset: 10, limit: 3))
diff --git a/spec/models/query-range-spec.coffee b/spec/models/query-range-spec.coffee
new file mode 100644
index 000000000..4c65090b1
--- /dev/null
+++ b/spec/models/query-range-spec.coffee
@@ -0,0 +1,88 @@
+QueryRange = require '../../src/flux/models/query-range'
+
+describe "QueryRange", ->
+  describe "@infinite", ->
+    it "should return a query range with a null limit and offset", ->
+      infinite = QueryRange.infinite()
+      expect(infinite.limit).toBe(null)
+      expect(infinite.offset).toBe(null)
+
+  describe "@rangesBySubtracting", ->
+    it "should throw an exception if either range is infinite", ->
+      infinite = QueryRange.infinite()
+      expect ->
+        QueryRange.rangesBySubtracting(infinite, new QueryRange({offset: 0, limit: 10}))
+      .toThrow()
+      expect ->
+        QueryRange.rangesBySubtracting(new QueryRange({offset: 0, limit: 10}), infinite)
+      .toThrow()
+
+    it "should return one or more ranges created by punching the provided range", ->
+      test = ({a, b, result}) ->
+        expect(QueryRange.rangesBySubtracting(a, b)).toEqual(result)
+      test
+        a: new QueryRange(offset: 0, limit: 10),
+        b: new QueryRange(offset: 3, limit: 3),
+        result: [new QueryRange(offset: 0, limit: 3), new QueryRange(offset: 6, limit: 4)]
+      test
+        a: new QueryRange(offset: 0, limit: 10),
+        b: new QueryRange(offset: 3, limit: 10),
+        result: [new QueryRange(offset: 0, limit: 3)]
+      test
+        a: new QueryRange(offset: 0, limit: 10),
+        b: new QueryRange(offset: 0, limit: 10),
+        result: []
+      test
+        a: new QueryRange(offset: 5, limit: 10),
+        b: new QueryRange(offset: 0, limit: 4),
+        result: [new QueryRange(offset: 5, limit: 10)]
+      test
+        a: new QueryRange(offset: 5, limit: 10),
+        b: new QueryRange(offset: 0, limit: 8),
+        result: [new QueryRange(offset: 8, limit: 7)]
+
+  describe "isInfinite", ->
+    it "should return true for an infinite range, false otherwise", ->
+      infinite = QueryRange.infinite()
+      expect(infinite.isInfinite()).toBe(true)
+      expect(new QueryRange(offset:0, limit:4).isInfinite()).toBe(false)
+
+  describe "start", ->
+    it "should be an alias for offset", ->
+      expect((new QueryRange(offset:3, limit:4)).start).toBe(3)
+
+  describe "end", ->
+    it "should be offset + limit", ->
+      expect((new QueryRange(offset:3, limit:4)).end).toBe(7)
+
+  describe "isContiguousWith", ->
+    it "should return true if either range is infinite", ->
+      a = new QueryRange(offset:3, limit:4)
+      expect(a.isContiguousWith(QueryRange.infinite())).toBe(true)
+      expect(QueryRange.infinite().isContiguousWith(a)).toBe(true)
+
+    it "should return true if the ranges intersect or touch, false otherwise", ->
+      a = new QueryRange(offset:3, limit:4)
+      b = new QueryRange(offset:0, limit:2)
+      c = new QueryRange(offset:0, limit:3)
+      d = new QueryRange(offset:7, limit:10)
+      e = new QueryRange(offset:8, limit:10)
+
+      # True
+
+      expect(a.isContiguousWith(d)).toBe(true)
+      expect(d.isContiguousWith(a)).toBe(true)
+
+      expect(a.isContiguousWith(c)).toBe(true)
+      expect(c.isContiguousWith(a)).toBe(true)
+
+      # False
+
+      expect(a.isContiguousWith(b)).toBe(false)
+      expect(b.isContiguousWith(a)).toBe(false)
+
+      expect(a.isContiguousWith(e)).toBe(false)
+      expect(e.isContiguousWith(a)).toBe(false)
+
+      expect(b.isContiguousWith(e)).toBe(false)
+      expect(e.isContiguousWith(b)).toBe(false)
diff --git a/src/flux/models/mutable-query-result-set.coffee b/src/flux/models/mutable-query-result-set.coffee
index f770132ed..022686b4f 100644
--- a/src/flux/models/mutable-query-result-set.coffee
+++ b/src/flux/models/mutable-query-result-set.coffee
@@ -53,7 +53,7 @@ class MutableQueryResultSet extends QueryResultSet
         existingBefore = @_ids.slice(0, range.offset - @_offset)
 
       existingAfter = []
-      if currentEnd > rangeIdsEnd
+      if rangeIds.length is range.limit and currentEnd > rangeIdsEnd
         existingAfter = @_ids.slice(rangeIdsEnd - @_offset)
 
       @_ids = [].concat(existingBefore, rangeIds, existingAfter)
diff --git a/src/flux/models/query-range.coffee b/src/flux/models/query-range.coffee
index 4e479ac44..4459495a2 100644
--- a/src/flux/models/query-range.coffee
+++ b/src/flux/models/query-range.coffee
@@ -4,8 +4,8 @@ class QueryRange
 
   @rangeWithUnion: (a, b) ->
     return QueryRange.infinite() if a.isInfinite() or b.isInfinite()
-    if not a.intersects(b)
-      throw new Error('You cannot union ranges which do not overlap.')
+    if not a.isContiguousWith(b)
+      throw new Error('You cannot union ranges which do not touch or intersect.')
 
     new QueryRange
       start: Math.min(a.start, b.start)
@@ -48,7 +48,9 @@ class QueryRange
   isEqual: (b) ->
     return @start is b.start and @end is b.end
 
-  intersects: (b) ->
+  # Returns true if joining the two ranges would not result in empty space.
+  # ie: they intersect or touch
+  isContiguousWith: (b) ->
     return true if @isInfinite() or b.isInfinite()
     return @start <= b.start <= @end or @start <= b.end <= @end
 
diff --git a/src/flux/models/query-subscription.coffee b/src/flux/models/query-subscription.coffee
index bf68ded83..7e98a16f2 100644
--- a/src/flux/models/query-subscription.coffee
+++ b/src/flux/models/query-subscription.coffee
@@ -141,7 +141,7 @@ class QuerySubscription
     rangeQuery ?= @_query
 
     DatabaseStore.run(rangeQuery, {format: false}).then (results) =>
-      @_set = null unless @_set?.range().intersects(range)
+      @_set = null unless @_set?.range().isContiguousWith(range)
       @_set ?= new MutableQueryResultSet()
 
       if entireModels