diff --git a/Guides/Overlay.md b/Guides/Overlay.md
new file mode 100644
index 00000000..a2934654
--- /dev/null
+++ b/Guides/Overlay.md
@@ -0,0 +1,170 @@
+# Overlay
+
+[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Overlay.swift) | 
+ [Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/OverlayTests.swift)]
+
+Compose collections by overlaying the elements of one collection
+over an arbitrary region of another collection.
+
+Swift offers many interesting collections, for instance:
+
+- `Range<Int>` allows us to express the numbers in `0..<1000`
+   in an efficient way that does not allocate storage for each number.
+   
+- `Repeated<Int>` allows us to express, say, one thousand copies of the same value,
+   without allocating space for a thousand values.
+   
+- `LazyMapCollection` allows us to transform the elements of a collection on-demand,
+   without creating a copy of the source collection and eagerly transforming every element.
+
+- The collections in this package, such as `.chunked`, `.cycled`, `.joined`, and `.interspersed`,
+  similarly compute their elements on-demand.
+
+While these collections can be very efficient, it is difficult to compose them in to arbitrary datasets.
+If we have the Range `5..<10`, and want to insert a `0` in the middle of it, we would need to allocate storage
+for the entire collection, losing the benefits of `Range<Int>`. Similarly, if we have some numbers in storage
+(say, in an Array) and wish to insert a contiguous range in the middle of it, we have to allocate storage
+in the Array and cannot take advantage of `Range<Int>` memory efficiency.
+
+The `OverlayCollection` allows us to form arbitrary compositions without mutating
+or allocating storage for the result.
+
+```swift
+// 'numbers' is a composition of:
+// - Range<Int>, and
+// - CollectionOfOne<Int>
+
+let numbers = (5..<10).overlay.inserting(0, at: 7)
+
+for n in numbers {
+  // n: 5, 6, 0, 7, 8, 9
+  //          ^
+}
+```
+
+```swift
+// 'numbers' is a composition of:
+// - Array<Int>, and
+// - Range<Int>
+
+let rawdata = [3, 6, 1, 4, 6]
+let numbers = rawdata.overlay.inserting(contentsOf: 5..<10, at: 3)
+
+for n in numbers {
+  // n: 3, 6, 1, 5, 6, 7, 8, 9, 4, 6
+  //             ^^^^^^^^^^^^^
+}
+```
+
+We can also insert elements in to a `LazyMapCollection`:
+
+```swift
+enum ListItem {
+  case product(Product)
+  case callToAction
+}
+
+let products: [Product] = ...
+
+var listItems: some Collection<ListItem> {
+  products
+    .lazy.map { ListItem.product($0) }
+    .overlay.inserting(.callToAction, at: min(4, products.count))
+}
+
+for item in listItems {
+  // item: .product(A), .product(B), .product(C), .callToAction, .product(D), ...
+  //                                              ^^^^^^^^^^^^^
+}
+```
+
+## Detailed Design
+
+An `.overlay` member is added to all collections:
+
+```swift
+extension Collection {
+  public var overlay: OverlayCollectionNamespace<Self> { get }
+}
+```
+
+This member returns a wrapper structure, `OverlayCollectionNamespace`,
+which provides a similar suite of methods to the standard library's `RangeReplaceableCollection` protocol.  
+
+However, while `RangeReplaceableCollection` methods mutate the collection they are applied to,
+these methods return a new `OverlayCollection` value which substitutes the specified elements on-demand.
+
+```swift
+extension OverlayCollectionNamespace {
+
+  // Multiple elements:
+
+  public func replacingSubrange<Overlay>(
+    _ subrange: Range<Elements.Index>, with newElements: Overlay
+  ) -> OverlayCollection<Elements, Overlay>
+
+  public func appending<Overlay>(
+    contentsOf newElements: Overlay
+  ) -> OverlayCollection<Elements, Overlay>
+
+  public func inserting<Overlay>(
+    contentsOf newElements: Overlay, at position: Elements.Index
+  ) -> OverlayCollection<Elements, Overlay>
+
+  public func removingSubrange(
+    _ subrange: Range<Elements.Index>
+  ) -> OverlayCollection<Elements, EmptyCollection<Elements.Element>>
+  
+  // Single elements:
+  
+  public func appending(
+    _ element: Elements.Element
+  ) -> OverlayCollection<Elements, CollectionOfOne<Elements.Element>>
+
+  public func inserting(
+    _ element: Elements.Element, at position: Elements.Index
+  ) -> OverlayCollection<Elements, CollectionOfOne<Elements.Element>>
+
+  public func removing(
+    at position: Elements.Index
+  ) -> OverlayCollection<Elements, EmptyCollection<Elements.Element>>
+  
+}
+```
+
+`OverlayCollection` conforms to `BidirectionalCollection` when both the base and overlay collections conform.
+
+### Conditional Overlays
+
+In order to allow overlays to be applied conditionally, another function is added to all collections:
+
+```swift
+extension Collection {
+
+  public func overlay<Overlay>(
+    if condition: Bool,
+    _ makeOverlay: (OverlayCollectionNamespace<Self>) -> OverlayCollection<Self, Overlay>
+  ) -> OverlayCollection<Self, Overlay>
+  
+}
+```
+
+If the `condition` parameter is `true`, the `makeOverlay` closure is invoked to apply the desired overlay.
+If `condition` is `false`, the closure is not invoked, and the function returns a no-op overlay,
+containing the same elements as the base collection. 
+
+This allows overlays to be applied conditionally while still being usable as opaque return types:
+
+```swift
+func getNumbers(shouldInsert: Bool) -> some Collection<Int> {
+  (5..<10).overlay(if: shouldInsert) { $0.inserting(0, at: 7) }
+}
+
+for n in getNumbers(shouldInsert: true) {
+  // n: 5, 6, 0, 7, 8, 9
+}
+
+for n in getNumbers(shouldInsert: false) {
+  // n: 5, 6, 7, 8, 9
+}
+``` 
diff --git a/Sources/Algorithms/Overlay.swift b/Sources/Algorithms/Overlay.swift
new file mode 100644
index 00000000..c42be0a7
--- /dev/null
+++ b/Sources/Algorithms/Overlay.swift
@@ -0,0 +1,329 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift Algorithms open source project
+//
+// Copyright (c) 2020 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+/// A namespace for methods which return composed collections,
+/// formed by replacing a region of a base collection
+/// with another collection of elements.
+///
+/// Access the namespace via the `.overlay` member, available on all collections:
+///
+/// ```swift
+/// let base = 0..<5
+/// for n in base.overlay.inserting(42, at: 2) {
+///   print(n)
+/// }
+/// // Prints: 0, 1, 42, 2, 3, 4
+/// ```
+///
+public struct OverlayCollectionNamespace<Elements: Collection> {
+
+  public let elements: Elements
+
+  @inlinable
+  internal init(elements: Elements) {
+    self.elements = elements
+  }
+}
+
+extension Collection {
+
+  /// A namespace for methods which return composed collections,
+  /// formed by replacing a region of this collection
+  /// with another collection of elements.
+  ///
+  @inlinable
+  public var overlay: OverlayCollectionNamespace<Self> {
+    OverlayCollectionNamespace(elements: self)
+  }
+
+  /// If `condition` is true, returns an `OverlayCollection` by applying the given closure.
+  /// Otherwise, returns an `OverlayCollection` containing the same elements as this collection.
+  ///
+  /// The following example takes an array of products, lazily wraps them in a `ListItem` enum,
+  /// and conditionally inserts a call-to-action element if `showCallToAction` is true.
+  ///
+  /// ```swift
+  /// var listItems: some Collection<ListItem> {
+  ///   let products: [Product] = ...
+  ///   return products
+  ///     .lazy.map { 
+  ///       ListItem.product($0)
+  ///     }
+  ///     .overlay(if: showCallToAction) {
+  ///       $0.inserting(.callToAction, at: min(4, $0.elements.count))
+  ///     }
+  /// }
+  /// ```
+  ///
+  @inlinable
+  public func overlay<Overlay>(
+    if condition: Bool, _ makeOverlay: (OverlayCollectionNamespace<Self>) -> OverlayCollection<Self, Overlay>
+  ) -> OverlayCollection<Self, Overlay> {
+    if condition {
+      return makeOverlay(overlay)
+    } else {
+      return OverlayCollection(base: self, overlay: nil, replacedRange: startIndex..<startIndex)
+    }
+  }
+}
+
+extension OverlayCollectionNamespace {
+
+  @inlinable
+  public func replacingSubrange<Overlay>(
+    _ subrange: Range<Elements.Index>, with newElements: Overlay
+  ) -> OverlayCollection<Elements, Overlay> {
+    OverlayCollection(base: elements, overlay: newElements, replacedRange: subrange)
+  }
+
+  @inlinable
+  public func appending<Overlay>(
+    contentsOf newElements: Overlay
+  ) -> OverlayCollection<Elements, Overlay> {
+    replacingSubrange(elements.endIndex..<elements.endIndex, with: newElements)
+  }
+
+  @inlinable
+  public func inserting<Overlay>(
+    contentsOf newElements: Overlay, at position: Elements.Index
+  ) -> OverlayCollection<Elements, Overlay> {
+    replacingSubrange(position..<position, with: newElements)
+  }
+
+  @inlinable
+  public func removingSubrange(
+    _ subrange: Range<Elements.Index>
+  ) -> OverlayCollection<Elements, EmptyCollection<Elements.Element>> {
+    replacingSubrange(subrange, with: EmptyCollection())
+  }
+
+  @inlinable
+  public func appending(
+    _ element: Elements.Element
+  ) -> OverlayCollection<Elements, CollectionOfOne<Elements.Element>> {
+    appending(contentsOf: CollectionOfOne(element))
+  }
+
+  @inlinable
+  public func inserting(
+    _ element: Elements.Element, at position: Elements.Index
+  ) -> OverlayCollection<Elements, CollectionOfOne<Elements.Element>> {
+    inserting(contentsOf: CollectionOfOne(element), at: position)
+  }
+
+  @inlinable
+  public func removing(
+    at position: Elements.Index
+  ) -> OverlayCollection<Elements, EmptyCollection<Elements.Element>> {
+    removingSubrange(position..<elements.index(after: position))
+  }
+}
+
+/// A composed collections, formed by replacing a region of a base collection
+/// with another collection of elements.
+///
+/// To create an OverlayCollection, use the methods in the ``OverlayCollectionNamespace``
+/// namespace:
+///
+/// ```swift
+/// let base = 0..<5
+/// for n in base.overlay.inserting(42, at: 2) {
+///   print(n)
+/// }
+/// // Prints: 0, 1, 42, 2, 3, 4
+/// ```
+///
+public struct OverlayCollection<Base, Overlay>
+where Base: Collection, Overlay: Collection, Base.Element == Overlay.Element {
+
+  @usableFromInline
+  internal var base: Base
+
+  @usableFromInline
+  internal var overlay: Optional<Overlay>
+
+  @usableFromInline
+  internal var replacedRange: Range<Base.Index>
+
+  @inlinable
+  internal init(base: Base, overlay: Overlay?, replacedRange: Range<Base.Index>) {
+    self.base = base
+    self.overlay = overlay
+    self.replacedRange = replacedRange
+  }
+}
+
+extension OverlayCollection: Collection {
+
+  public typealias Element = Base.Element
+
+  public struct Index: Comparable {
+    
+    @usableFromInline
+    internal enum Wrapped {
+      case base(Base.Index)
+      case overlay(Overlay.Index)
+    }
+
+    /// The underlying base/overlay index.
+    ///
+    @usableFromInline
+    internal var wrapped: Wrapped
+
+    /// The base index at which the overlay starts -- i.e. `replacedRange.lowerBound`
+    ///
+    @usableFromInline
+    internal var startOfReplacedRange: Base.Index
+
+    @inlinable
+    internal init(wrapped: Wrapped, startOfReplacedRange: Base.Index) {
+      self.wrapped = wrapped
+      self.startOfReplacedRange = startOfReplacedRange
+    }
+
+    @inlinable
+    public static func < (lhs: Self, rhs: Self) -> Bool {
+      switch (lhs.wrapped, rhs.wrapped) {
+      case (.base(let unwrappedLeft), .base(let unwrappedRight)):
+        return unwrappedLeft < unwrappedRight
+      case (.overlay(let unwrappedLeft), .overlay(let unwrappedRight)):
+        return unwrappedLeft < unwrappedRight
+      case (.base(let unwrappedLeft), .overlay(_)):
+        return unwrappedLeft < lhs.startOfReplacedRange
+      case (.overlay(_), .base(let unwrappedRight)):
+        return !(unwrappedRight < lhs.startOfReplacedRange)
+      }
+    }
+
+    @inlinable
+    public static func == (lhs: Self, rhs: Self) -> Bool {
+      // No need to check 'startOfReplacedRange', because it does not differ between indices from the same collection.
+      switch (lhs.wrapped, rhs.wrapped) {
+      case (.base(let unwrappedLeft), .base(let unwrappedRight)):
+        return unwrappedLeft == unwrappedRight
+      case (.overlay(let unwrappedLeft), .overlay(let unwrappedRight)):
+        return unwrappedLeft == unwrappedRight
+      default:
+        return false
+      }
+    }
+  }
+}
+
+extension OverlayCollection {
+
+  @inlinable
+  internal func makeIndex(_ position: Base.Index) -> Index {
+    Index(wrapped: .base(position), startOfReplacedRange: replacedRange.lowerBound)
+  }
+
+  @inlinable
+  internal func makeIndex(_ position: Overlay.Index) -> Index {
+    Index(wrapped: .overlay(position), startOfReplacedRange: replacedRange.lowerBound)
+  }
+
+  @inlinable
+  public var startIndex: Index {
+    if let overlay = overlay, base.startIndex == replacedRange.lowerBound {
+      if overlay.isEmpty {
+        return makeIndex(replacedRange.upperBound)
+      }
+      return makeIndex(overlay.startIndex)
+    }
+    return makeIndex(base.startIndex)
+  }
+
+  @inlinable
+  public var endIndex: Index {
+    guard let overlay = overlay else {
+      return makeIndex(base.endIndex)
+    }
+    if replacedRange.lowerBound != base.endIndex || overlay.isEmpty {
+      return makeIndex(base.endIndex)
+    }
+    return makeIndex(overlay.endIndex)
+  }
+
+  @inlinable
+  public var count: Int {
+    guard let overlay = overlay else {
+      return base.count
+    }
+    return base.distance(from: base.startIndex, to: replacedRange.lowerBound)
+    + overlay.count
+    + base.distance(from: replacedRange.upperBound, to: base.endIndex)
+  }
+
+  @inlinable
+  public var isEmpty: Bool {
+    return replacedRange.lowerBound == base.startIndex
+    && replacedRange.upperBound == base.endIndex
+    && (overlay?.isEmpty ?? true)
+  }
+
+  @inlinable
+  public func index(after i: Index) -> Index {
+    switch i.wrapped {
+    case .base(var baseIndex):
+      base.formIndex(after: &baseIndex)
+      if let overlay = overlay, baseIndex == replacedRange.lowerBound {
+        if overlay.isEmpty {
+          return makeIndex(replacedRange.upperBound)
+        }
+        return makeIndex(overlay.startIndex)
+      }
+      return makeIndex(baseIndex)
+
+    case .overlay(var overlayIndex):
+      overlay!.formIndex(after: &overlayIndex)
+      if replacedRange.lowerBound != base.endIndex, overlayIndex == overlay!.endIndex {
+        return makeIndex(replacedRange.upperBound)
+      }
+      return makeIndex(overlayIndex)
+    }
+  }
+
+  @inlinable
+  public subscript(position: Index) -> Element {
+    switch position.wrapped {
+    case .base(let baseIndex): 
+      return base[baseIndex]
+    case .overlay(let overlayIndex):
+      return overlay![overlayIndex]
+    }
+  }
+}
+
+extension OverlayCollection: BidirectionalCollection
+where Base: BidirectionalCollection, Overlay: BidirectionalCollection {
+
+  @inlinable
+  public func index(before i: Index) -> Index {
+    switch i.wrapped {
+    case .base(var baseIndex):
+      if let overlay = overlay, baseIndex == replacedRange.upperBound {
+        if overlay.isEmpty {
+          return makeIndex(base.index(before: replacedRange.lowerBound))
+        }
+        return makeIndex(overlay.index(before: overlay.endIndex))
+      }
+      base.formIndex(before: &baseIndex)
+      return makeIndex(baseIndex)
+
+    case .overlay(var overlayIndex):
+      if overlayIndex == overlay!.startIndex {
+        return makeIndex(base.index(before: replacedRange.lowerBound))
+      }
+      overlay!.formIndex(before: &overlayIndex)
+      return makeIndex(overlayIndex)
+    }
+  }
+}
diff --git a/Tests/SwiftAlgorithmsTests/OverlayTests.swift b/Tests/SwiftAlgorithmsTests/OverlayTests.swift
new file mode 100644
index 00000000..19c20d17
--- /dev/null
+++ b/Tests/SwiftAlgorithmsTests/OverlayTests.swift
@@ -0,0 +1,343 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift Algorithms open source project
+//
+// Copyright (c) 2020 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+//
+//===----------------------------------------------------------------------===//
+
+import XCTest
+@testable import Algorithms
+
+final class ReplaceSubrangeTests: XCTestCase {
+
+  func testAppend() {
+
+    func _performAppendTest<Base, Overlay>(
+      base: Base, appending newElements: Overlay,
+      _ checkResult: (OverlayCollection<Base, Overlay>) -> Void
+    ) {
+      checkResult(base.overlay.appending(contentsOf: newElements))
+
+      checkResult(base.overlay.inserting(contentsOf: newElements, at: base.endIndex))
+
+      checkResult(base.overlay.replacingSubrange(base.endIndex..<base.endIndex, with: newElements))
+    }
+
+    // Base: non-empty
+    // Appending: non-empty
+    _performAppendTest(base: 0..<5, appending: [8, 9, 10]) { result in
+      XCTAssertEqualCollections(result, [0, 1, 2, 3, 4, 8, 9, 10])
+      IndexValidator().validate(result, expectedCount: 8)
+    }
+
+    // Base: non-empty
+    // Appending: empty
+    _performAppendTest(base: 0..<5, appending: EmptyCollection()) { result in
+      XCTAssertEqualCollections(result, [0, 1, 2, 3, 4])
+      IndexValidator().validate(result, expectedCount: 5)
+    }
+
+    // Base: empty
+    // Appending: non-empty
+    _performAppendTest(base: EmptyCollection(), appending: 5..<10) { result in
+      XCTAssertEqualCollections(result, [5, 6, 7, 8, 9])
+      IndexValidator().validate(result, expectedCount: 5)
+    }
+
+    // Base: empty
+    // Appending: empty
+    _performAppendTest(base: EmptyCollection<Int>(), appending: EmptyCollection()) { result in
+      XCTAssertEqualCollections(result, [])
+      IndexValidator().validate(result, expectedCount: 0)
+    }
+  }
+
+  func testAppendSingle() {
+
+    // Base: empty
+    do {
+      let base = EmptyCollection<Int>()
+      let result = base.overlay.appending(99)
+      XCTAssertEqualCollections(result, [99])
+      IndexValidator().validate(result, expectedCount: 1)
+    }
+
+    // Base: non-empty
+    do {
+      let base = 2..<8
+      let result = base.overlay.appending(99)
+      XCTAssertEqualCollections(result, [2, 3, 4, 5, 6, 7, 99])
+      IndexValidator().validate(result, expectedCount: 7)
+    }
+  }
+
+  func testPrepend() {
+
+    func _performPrependTest<Base, Overlay>(
+      base: Base, prepending newElements: Overlay,
+      _ checkResult: (OverlayCollection<Base, Overlay>) -> Void
+    ) {
+      checkResult(base.overlay.inserting(contentsOf: newElements, at: base.startIndex))
+
+      checkResult(base.overlay.replacingSubrange(base.startIndex..<base.startIndex, with: newElements))
+    }
+
+    // Base: non-empty
+    // Prepending: non-empty
+    _performPrependTest(base: 0..<5, prepending: [8, 9, 10]) { result in
+      XCTAssertEqualCollections(result, [8, 9, 10, 0, 1, 2, 3, 4])
+      IndexValidator().validate(result, expectedCount: 8)
+    }
+
+    // Base: non-empty
+    // Prepending: empty
+    _performPrependTest(base: 0..<5, prepending: EmptyCollection()) { result in
+      XCTAssertEqualCollections(result, [0, 1, 2, 3, 4])
+      IndexValidator().validate(result, expectedCount: 5)
+    }
+
+    // Base: empty
+    // Prepending: non-empty
+    _performPrependTest(base: EmptyCollection(), prepending: 5..<10) { result in
+      XCTAssertEqualCollections(result, [5, 6, 7, 8, 9])
+      IndexValidator().validate(result, expectedCount: 5)
+    }
+
+    // Base: empty
+    // Prepending: empty
+    _performPrependTest(base: EmptyCollection<Int>(), prepending: EmptyCollection()) { result in
+      XCTAssertEqualCollections(result, [])
+      IndexValidator().validate(result, expectedCount: 0)
+    }
+  }
+
+  func testPrependSingle() {
+
+    // Base: empty
+    do {
+      let base = EmptyCollection<Int>()
+      let result = base.overlay.inserting(99, at: base.startIndex)
+      XCTAssertEqualCollections(result, [99])
+      IndexValidator().validate(result, expectedCount: 1)
+    }
+
+    // Base: non-empty
+    do {
+      let base = 2..<8
+      let result = base.overlay.inserting(99, at: base.startIndex)
+      XCTAssertEqualCollections(result, [99, 2, 3, 4, 5, 6, 7])
+      IndexValidator().validate(result, expectedCount: 7)
+    }
+  }
+
+  func testInsert() {
+
+    // Inserting: non-empty
+    do {
+      let base = 0..<10
+      let i = base.index(base.startIndex, offsetBy: 5)
+      let result = base.overlay.inserting(contentsOf: 20..<25, at: i)
+      XCTAssertEqualCollections(result, [0, 1, 2, 3, 4, 20, 21, 22, 23, 24, 5, 6, 7, 8, 9])
+      IndexValidator().validate(result, expectedCount: 15)
+    }
+
+    // Inserting: empty
+    do {
+      let base = 0..<10
+      let i = base.index(base.startIndex, offsetBy: 5)
+      let result = base.overlay.inserting(contentsOf: EmptyCollection(), at: i)
+      XCTAssertEqualCollections(result, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
+      IndexValidator().validate(result, expectedCount: 10)
+    }
+  }
+
+  func testInsertSingle() {
+
+    let base = 2..<8
+    let result = base.overlay.inserting(99, at: base.index(base.startIndex, offsetBy: 3))
+    XCTAssertEqualCollections(result, [2, 3, 4, 99, 5, 6, 7])
+    IndexValidator().validate(result, expectedCount: 7)
+  }
+
+  func testReplace() {
+
+    // Location: anchored to start
+    // Replacement: non-empty
+    do {
+      let base = "hello, world!"
+      let i = base.index(base.startIndex, offsetBy: 3)
+      let result = base.overlay.replacingSubrange(base.startIndex..<i, with: "goodbye".reversed())
+      XCTAssertEqualCollections(result, "eybdooglo, world!")
+      IndexValidator().validate(result, expectedCount: 17)
+    }
+
+    // Location: anchored to start
+    // Replacement: empty
+    do {
+      let base = "hello, world!"
+      let i = base.index(base.startIndex, offsetBy: 3)
+      let result = base.overlay.replacingSubrange(base.startIndex..<i, with: EmptyCollection())
+      XCTAssertEqualCollections(result, "lo, world!")
+      IndexValidator().validate(result, expectedCount: 10)
+    }
+
+    // Location: middle
+    // Replacement: non-empty
+    do {
+      let base = "hello, world!"
+      let start = base.index(base.startIndex, offsetBy: 3)
+      let end = base.index(start, offsetBy: 4)
+      let result = base.overlay.replacingSubrange(start..<end, with: "goodbye".reversed())
+      XCTAssertEqualCollections(result, "heleybdoogworld!")
+      IndexValidator().validate(result, expectedCount: 16)
+    }
+
+    // Location: middle
+    // Replacement: empty
+    do {
+      let base = "hello, world!"
+      let start = base.index(base.startIndex, offsetBy: 3)
+      let end = base.index(start, offsetBy: 4)
+      let result = base.overlay.replacingSubrange(start..<end, with: EmptyCollection())
+      XCTAssertEqualCollections(result, "helworld!")
+      IndexValidator().validate(result, expectedCount: 9)
+    }
+
+    // Location: anchored to end
+    // Replacement: non-empty
+    do {
+      let base = "hello, world!"
+      let start = base.index(base.endIndex, offsetBy: -4)
+      let result = base.overlay.replacingSubrange(start..<base.endIndex, with: "goodbye".reversed())
+      XCTAssertEqualCollections(result, "hello, woeybdoog")
+      IndexValidator().validate(result, expectedCount: 16)
+    }
+
+    // Location: anchored to end
+    // Replacement: empty
+    do {
+      let base = "hello, world!"
+      let start = base.index(base.endIndex, offsetBy: -4)
+      let result = base.overlay.replacingSubrange(start..<base.endIndex, with: EmptyCollection())
+      XCTAssertEqualCollections(result, "hello, wo")
+      IndexValidator().validate(result, expectedCount: 9)
+    }
+
+    // Location: entire collection
+    // Replacement: non-empty
+    do {
+      let base = "hello, world!"
+      let result = base.overlay.replacingSubrange(base.startIndex..<base.endIndex, with: Array("blah blah blah"))
+      XCTAssertEqualCollections(result, "blah blah blah")
+      IndexValidator().validate(result, expectedCount: 14)
+    }
+
+    // Location: entire collection
+    // Replacement: empty
+    do {
+      let base = "hello, world!"
+      let result = base.overlay.replacingSubrange(base.startIndex..<base.endIndex, with: EmptyCollection())
+      XCTAssertEqualCollections(result, "")
+      IndexValidator().validate(result, expectedCount: 0)
+    }
+  }
+
+  func testRemove() {
+
+    // Location: anchored to start
+    do {
+      let base = "hello, world!"
+      let i = base.index(base.startIndex, offsetBy: 3)
+      let result = base.overlay.removingSubrange(base.startIndex..<i)
+      XCTAssertEqualCollections(result, "lo, world!")
+      IndexValidator().validate(result, expectedCount: 10)
+    }
+
+    // Location: middle
+    do {
+      let base = "hello, world!"
+      let start = base.index(base.startIndex, offsetBy: 3)
+      let end = base.index(start, offsetBy: 4)
+      let result = base.overlay.removingSubrange(start..<end)
+      XCTAssertEqualCollections(result, "helworld!")
+      IndexValidator().validate(result, expectedCount: 9)
+    }
+
+    // Location: anchored to end
+    do {
+      let base = "hello, world!"
+      let start = base.index(base.endIndex, offsetBy: -4)
+      let result = base.overlay.removingSubrange(start..<base.endIndex)
+      XCTAssertEqualCollections(result, "hello, wo")
+      IndexValidator().validate(result, expectedCount: 9)
+    }
+
+    // Location: entire collection
+    do {
+      let base = "hello, world!"
+      let result = base.overlay.removingSubrange(base.startIndex..<base.endIndex)
+      XCTAssertEqualCollections(result, "")
+      IndexValidator().validate(result, expectedCount: 0)
+    }
+  }
+
+  func testRemoveSingle() {
+
+    // Location: start
+    do {
+      let base = "hello, world!"
+      let result = base.overlay.removing(at: base.startIndex)
+      XCTAssertEqualCollections(result, "ello, world!")
+      IndexValidator().validate(result, expectedCount: 12)
+    }
+
+    // Location: middle
+    do {
+      let base = "hello, world!"
+      let i = base.index(base.startIndex, offsetBy: 3)
+      let result = base.overlay.removing(at: i)
+      XCTAssertEqualCollections(result, "helo, world!")
+      IndexValidator().validate(result, expectedCount: 12)
+    }
+
+    // Location: end
+    do {
+      let base = "hello, world!"
+      let i = base.index(before: base.endIndex)
+      let result = base.overlay.removing(at: i)
+      XCTAssertEqualCollections(result, "hello, world")
+      IndexValidator().validate(result, expectedCount: 12)
+    }
+
+    // Location: entire collection
+    do {
+      let base = "x"
+      let result = base.overlay.removing(at: base.startIndex)
+      XCTAssertEqualCollections(result, "")
+      IndexValidator().validate(result, expectedCount: 0)
+    }
+  }
+
+  func testConditionalReplacement() {
+
+    func getNumbers(shouldInsert: Bool) -> OverlayCollection<Range<Int>, CollectionOfOne<Int>> {
+      (0..<5).overlay(if: shouldInsert) { $0.inserting(42, at: 2) }
+    }
+
+    do {
+      let result = getNumbers(shouldInsert: true)
+      XCTAssertEqualCollections(result, [0, 1, 42, 2, 3, 4])
+      IndexValidator().validate(result, expectedCount: 6)
+    }
+
+    do {
+      let result = getNumbers(shouldInsert: false)
+      XCTAssertEqualCollections(result, [0, 1, 2, 3, 4])
+      IndexValidator().validate(result, expectedCount: 5)
+    }
+  }
+}