diff --git a/src/Init/Data/List/Basic.lean b/src/Init/Data/List/Basic.lean
index 8a7631d03b11..f0d989e9ecd8 100644
--- a/src/Init/Data/List/Basic.lean
+++ b/src/Init/Data/List/Basic.lean
@@ -38,7 +38,7 @@ The operations are organized as follow:
 * Sublists: `take`, `drop`, `takeWhile`, `dropWhile`, `partition`, `dropLast`,
   `isPrefixOf`, `isPrefixOf?`, `isSuffixOf`, `isSuffixOf?`, `Subset`, `Sublist`,
   `rotateLeft` and `rotateRight`.
-* Manipulating elements: `replace`, `insert`, `modify`, `erase`, `eraseP`, `eraseIdx`.
+* Manipulating elements: `replace`, `modify`, `insert`, `insertIdx`, `erase`, `eraseP`, `eraseIdx`.
 * Finding elements: `find?`, `findSome?`, `findIdx`, `indexOf`, `findIdx?`, `indexOf?`,
  `countP`, `count`, and `lookup`.
 * Logic: `any`, `all`, `or`, and `and`.
@@ -1113,12 +1113,6 @@ theorem replace_cons [BEq α] {a : α} :
     (a::as).replace b c = match b == a with | true => c::as | false => a :: replace as b c :=
   rfl
 
-/-! ### insert -/
-
-/-- Inserts an element into a list without duplication. -/
-@[inline] protected def insert [BEq α] (a : α) (l : List α) : List α :=
-  if l.elem a then l else a :: l
-
 /-! ### modify -/
 
 /--
@@ -1148,6 +1142,21 @@ Apply `f` to the nth element of the list, if it exists, replacing that element w
 def modify (f : α → α) : Nat → List α → List α :=
   modifyTailIdx (modifyHead f)
 
+/-! ### insert -/
+
+/-- Inserts an element into a list without duplication. -/
+@[inline] protected def insert [BEq α] (a : α) (l : List α) : List α :=
+  if l.elem a then l else a :: l
+
+/--
+`insertIdx n a l` inserts `a` into the list `l` after the first `n` elements of `l`
+```
+insertIdx 2 1 [1, 2, 3, 4] = [1, 2, 1, 3, 4]
+```
+-/
+def insertIdx (n : Nat) (a : α) : List α → List α :=
+  modifyTailIdx (cons a) n
+
 /-! ### erase -/
 
 /--
diff --git a/src/Init/Data/List/Impl.lean b/src/Init/Data/List/Impl.lean
index 9e85de85bcef..430321ab12a9 100644
--- a/src/Init/Data/List/Impl.lean
+++ b/src/Init/Data/List/Impl.lean
@@ -38,7 +38,7 @@ The following operations were already given `@[csimp]` replacements in `Init/Dat
 
 The following operations are given `@[csimp]` replacements below:
 `set`, `filterMap`, `foldr`, `append`, `bind`, `join`,
-`take`, `takeWhile`, `dropLast`, `replace`, `modify`, `erase`, `eraseIdx`, `zipWith`,
+`take`, `takeWhile`, `dropLast`, `replace`, `modify`, `insertIdx`, `erase`, `eraseIdx`, `zipWith`,
 `enumFrom`, and `intercalate`.
 
 -/
@@ -215,6 +215,23 @@ theorem modifyTR_go_eq : ∀ l n, modifyTR.go f l n acc = acc.toList ++ modify f
 @[csimp] theorem modify_eq_modifyTR : @modify = @modifyTR := by
   funext α f n l; simp [modifyTR, modifyTR_go_eq]
 
+/-! ### insertIdx -/
+
+/-- Tail-recursive version of `insertIdx`. -/
+@[inline] def insertIdxTR (n : Nat) (a : α) (l : List α) : List α := go n l #[] where
+  /-- Auxiliary for `insertIdxTR`: `insertIdxTR.go a n l acc = acc.toList ++ insertIdx n a l`. -/
+  go : Nat → List α → Array α → List α
+  | 0, l, acc => acc.toListAppend (a :: l)
+  | _, [], acc => acc.toList
+  | n+1, a :: l, acc => go n l (acc.push a)
+
+theorem insertIdxTR_go_eq : ∀ n l, insertIdxTR.go a n l acc = acc.toList ++ insertIdx n a l
+  | 0, l | _+1, [] => by simp [insertIdxTR.go, insertIdx]
+  | n+1, a :: l => by simp [insertIdxTR.go, insertIdx, insertIdxTR_go_eq n l]
+
+@[csimp] theorem insertIdx_eq_insertIdxTR : @insertIdx = @insertIdxTR := by
+  funext α f n l; simp [insertIdxTR, insertIdxTR_go_eq]
+
 /-! ### erase -/
 
 /-- Tail recursive version of `List.erase`. -/
diff --git a/src/Init/Data/List/Nat.lean b/src/Init/Data/List/Nat.lean
index 6b34ca651432..17385c3719f8 100644
--- a/src/Init/Data/List/Nat.lean
+++ b/src/Init/Data/List/Nat.lean
@@ -14,3 +14,4 @@ import Init.Data.List.Nat.Erase
 import Init.Data.List.Nat.Find
 import Init.Data.List.Nat.BEq
 import Init.Data.List.Nat.Modify
+import Init.Data.List.Nat.InsertIdx
diff --git a/src/Init/Data/List/Nat/InsertIdx.lean b/src/Init/Data/List/Nat/InsertIdx.lean
new file mode 100644
index 000000000000..3fcda63c25fc
--- /dev/null
+++ b/src/Init/Data/List/Nat/InsertIdx.lean
@@ -0,0 +1,242 @@
+/-
+Copyright (c) 2014 Parikshit Khanna. All rights reserved.
+Released under Apache 2.0 license as described in the file LICENSE.
+Authors: Parikshit Khanna, Jeremy Avigad, Leonardo de Moura, Floris van Doorn, Mario Carneiro
+-/
+prelude
+import Init.Data.List.Nat.Modify
+
+/-!
+# insertIdx
+
+Proves various lemmas about `List.insertIdx`.
+-/
+
+open Function
+
+open Nat
+
+namespace List
+
+universe u
+
+variable {α : Type u}
+
+section InsertIdx
+
+variable {a : α}
+
+@[simp]
+theorem insertIdx_zero (s : List α) (x : α) : insertIdx 0 x s = x :: s :=
+  rfl
+
+@[simp]
+theorem insertIdx_succ_nil (n : Nat) (a : α) : insertIdx (n + 1) a [] = [] :=
+  rfl
+
+@[simp]
+theorem insertIdx_succ_cons (s : List α) (hd x : α) (n : Nat) :
+    insertIdx (n + 1) x (hd :: s) = hd :: insertIdx n x s :=
+  rfl
+
+theorem length_insertIdx : ∀ n as, (insertIdx n a as).length = if n ≤ as.length then as.length + 1 else as.length
+  | 0, _ => by simp
+  | n + 1, [] => by simp
+  | n + 1, a :: as => by
+    simp only [insertIdx_succ_cons, length_cons, length_insertIdx, Nat.add_le_add_iff_right]
+    split <;> rfl
+
+theorem length_insertIdx_of_le_length (h : n ≤ length as) : length (insertIdx n a as) = length as + 1 := by
+  simp [length_insertIdx, h]
+
+theorem length_insertIdx_of_length_lt (h : length as < n) : length (insertIdx n a as) = length as := by
+  simp [length_insertIdx, h]
+
+theorem eraseIdx_insertIdx (n : Nat) (l : List α) : (l.insertIdx n a).eraseIdx n = l := by
+  rw [eraseIdx_eq_modifyTailIdx, insertIdx, modifyTailIdx_modifyTailIdx_eq]
+  exact modifyTailIdx_id _ _
+
+theorem insertIdx_eraseIdx_of_ge :
+    ∀ n m as,
+      n < length as → n ≤ m → insertIdx m a (as.eraseIdx n) = (as.insertIdx (m + 1) a).eraseIdx n
+  | 0, 0, [], has, _ => (Nat.lt_irrefl _ has).elim
+  | 0, 0, _ :: as, _, _ => by simp [eraseIdx, insertIdx]
+  | 0, _ + 1, _ :: _, _, _ => rfl
+  | n + 1, m + 1, a :: as, has, hmn =>
+    congrArg (cons a) <|
+      insertIdx_eraseIdx_of_ge n m as (Nat.lt_of_succ_lt_succ has) (Nat.le_of_succ_le_succ hmn)
+
+theorem insertIdx_eraseIdx_of_le :
+    ∀ n m as,
+      n < length as → m ≤ n → insertIdx m a (as.eraseIdx n) = (as.insertIdx m a).eraseIdx (n + 1)
+  | _, 0, _ :: _, _, _ => rfl
+  | n + 1, m + 1, a :: as, has, hmn =>
+    congrArg (cons a) <|
+      insertIdx_eraseIdx_of_le n m as (Nat.lt_of_succ_lt_succ has) (Nat.le_of_succ_le_succ hmn)
+
+theorem insertIdx_comm (a b : α) :
+    ∀ (i j : Nat) (l : List α) (_ : i ≤ j) (_ : j ≤ length l),
+      (l.insertIdx i a).insertIdx (j + 1) b = (l.insertIdx j b).insertIdx i a
+  | 0, j, l => by simp [insertIdx]
+  | _ + 1, 0, _ => fun h => (Nat.not_lt_zero _ h).elim
+  | i + 1, j + 1, [] => by simp
+  | i + 1, j + 1, c :: l => fun h₀ h₁ => by
+    simp only [insertIdx_succ_cons, cons.injEq, true_and]
+    exact insertIdx_comm a b i j l (Nat.le_of_succ_le_succ h₀) (Nat.le_of_succ_le_succ h₁)
+
+theorem mem_insertIdx {a b : α} :
+    ∀ {n : Nat} {l : List α} (_ : n ≤ l.length), a ∈ l.insertIdx n b ↔ a = b ∨ a ∈ l
+  | 0, as, _ => by simp
+  | _ + 1, [], h => (Nat.not_succ_le_zero _ h).elim
+  | n + 1, a' :: as, h => by
+    rw [List.insertIdx_succ_cons, mem_cons, mem_insertIdx (Nat.le_of_succ_le_succ h),
+      ← or_assoc, @or_comm (a = a'), or_assoc, mem_cons]
+
+theorem insertIdx_of_length_lt (l : List α) (x : α) (n : Nat) (h : l.length < n) :
+    insertIdx n x l = l := by
+  induction l generalizing n with
+  | nil =>
+    cases n
+    · simp at h
+    · simp
+  | cons x l ih =>
+    cases n
+    · simp at h
+    · simp only [Nat.succ_lt_succ_iff, length] at h
+      simpa using ih _ h
+
+@[simp]
+theorem insertIdx_length_self (l : List α) (x : α) : insertIdx l.length x l = l ++ [x] := by
+  induction l with
+  | nil => simp
+  | cons x l ih => simpa using ih
+
+theorem length_le_length_insertIdx (l : List α) (x : α) (n : Nat) :
+    l.length ≤ (insertIdx n x l).length := by
+  simp only [length_insertIdx]
+  split <;> simp
+
+theorem length_insertIdx_le_succ (l : List α) (x : α) (n : Nat) :
+    (insertIdx n x l).length ≤ l.length + 1 := by
+  simp only [length_insertIdx]
+  split <;> simp
+
+theorem getElem_insertIdx_of_lt {l : List α} {x : α} {n k : Nat} (hn : k < n)
+    (hk : k < (insertIdx n x l).length) :
+    (insertIdx n x l)[k] = l[k]'(by simp [length_insertIdx] at hk; split at hk <;> omega) := by
+  induction n generalizing k l with
+  | zero => simp at hn
+  | succ n ih =>
+    cases l with
+    | nil => simp
+    | cons _ _=>
+      cases k
+      · simp [get]
+      · rw [Nat.succ_lt_succ_iff] at hn
+        simpa using ih hn _
+
+@[simp]
+theorem getElem_insertIdx_self {l : List α} {x : α} {n : Nat} (hn : n < (insertIdx n x l).length) :
+    (insertIdx n x l)[n] = x := by
+  induction l generalizing n with
+  | nil =>
+    simp [length_insertIdx] at hn
+    split at hn
+    · simp_all
+    · omega
+  | cons _ _ ih =>
+    cases n
+    · simp
+    · simp only [insertIdx_succ_cons, length_cons, length_insertIdx, Nat.add_lt_add_iff_right] at hn ih
+      simpa using ih hn
+
+theorem getElem_insertIdx_of_ge {l : List α} {x : α} {n k : Nat} (hn : n + 1 ≤ k)
+    (hk : k < (insertIdx n x l).length) :
+    (insertIdx n x l)[k] = l[k - 1]'(by simp [length_insertIdx] at hk; split at hk <;> omega) := by
+  induction l generalizing n k with
+  | nil =>
+    cases n with
+    | zero =>
+      simp only [insertIdx_zero, length_singleton, lt_one_iff] at hk
+      omega
+    | succ n => simp at hk
+  | cons _ _ ih =>
+    cases n with
+    | zero =>
+      simp only [insertIdx_zero] at hk
+      cases k with
+      | zero => omega
+      | succ k => simp
+    | succ n =>
+      cases k with
+      | zero => simp
+      | succ k =>
+        simp only [insertIdx_succ_cons, getElem_cons_succ]
+        rw [ih (by omega)]
+        cases k with
+        | zero => omega
+        | succ k => simp
+
+theorem getElem_insertIdx {l : List α} {x : α} {n k : Nat} (h : k < (insertIdx n x l).length) :
+    (insertIdx n x l)[k] =
+      if h₁ : k < n then
+        l[k]'(by simp [length_insertIdx] at h; split at h <;> omega)
+      else
+        if h₂ : k = n then
+          x
+        else
+          l[k-1]'(by simp [length_insertIdx] at h; split at h <;> omega) := by
+  split <;> rename_i h₁
+  · rw [getElem_insertIdx_of_lt h₁]
+  · split <;> rename_i h₂
+    · subst h₂
+      rw [getElem_insertIdx_self h]
+    · rw [getElem_insertIdx_of_ge (by omega)]
+
+theorem getElem?_insertIdx {l : List α} {x : α} {n k : Nat} :
+    (insertIdx n x l)[k]? =
+      if k < n then
+        l[k]?
+      else
+        if k = n then
+          if k ≤ l.length then some x else none
+        else
+          l[k-1]? := by
+  rw [getElem?_def]
+  split <;> rename_i h
+  · rw [getElem_insertIdx h]
+    simp only [length_insertIdx] at h
+    split <;> rename_i h₁
+    · rw [getElem?_def, dif_pos]
+    · split <;> rename_i h₂
+      · rw [if_pos]
+        split at h <;> omega
+      · rw [getElem?_def]
+        simp only [Option.some_eq_dite_none_right, exists_prop, and_true]
+        split at h <;> omega
+  · simp only [length_insertIdx] at h
+    split <;> rename_i h₁
+    · rw [getElem?_eq_none]
+      split at h <;> omega
+    · split <;> rename_i h₂
+      · rw [if_neg]
+        split at h <;> omega
+      · rw [getElem?_eq_none]
+        split at h <;> omega
+
+theorem getElem?_insertIdx_of_lt {l : List α} {x : α} {n k : Nat} (h : k < n) :
+    (insertIdx n x l)[k]? = l[k]? := by
+  rw [getElem?_insertIdx, if_pos h]
+
+theorem getElem?_insertIdx_self {l : List α} {x : α} {n : Nat} :
+    (insertIdx n x l)[n]? = if n ≤ l.length then some x else none := by
+  rw [getElem?_insertIdx, if_neg (by omega)]
+  simp
+
+theorem getElem?_insertIdx_of_ge {l : List α} {x : α} {n k : Nat} (h : n + 1 ≤ k) :
+    (insertIdx n x l)[k]? = l[k - 1]? := by
+  rw [getElem?_insertIdx, if_neg (by omega), if_neg (by omega)]
+
+end InsertIdx
+
+end List
diff --git a/src/Init/Data/List/Nat/Modify.lean b/src/Init/Data/List/Nat/Modify.lean
index c1a112e9fe7f..9f88f5ed36df 100644
--- a/src/Init/Data/List/Nat/Modify.lean
+++ b/src/Init/Data/List/Nat/Modify.lean
@@ -110,6 +110,25 @@ theorem exists_of_modifyTailIdx (f : List α → List α) {n} {l : List α} (h :
     ⟨_, _, (take_append_drop n l).symm, length_take_of_le h⟩
   ⟨_, _, eq, hl, hl ▸ eq ▸ modifyTailIdx_add (n := 0) ..⟩
 
+theorem modifyTailIdx_modifyTailIdx {f g : List α → List α} (m : Nat) :
+    ∀ (n) (l : List α),
+      (l.modifyTailIdx f n).modifyTailIdx g (m + n) =
+        l.modifyTailIdx (fun l => (f l).modifyTailIdx g m) n
+  | 0, _ => rfl
+  | _ + 1, [] => rfl
+  | n + 1, a :: l => congrArg (List.cons a) (modifyTailIdx_modifyTailIdx m n l)
+
+theorem modifyTailIdx_modifyTailIdx_le {f g : List α → List α} (m n : Nat) (l : List α)
+    (h : n ≤ m) :
+    (l.modifyTailIdx f n).modifyTailIdx g m =
+      l.modifyTailIdx (fun l => (f l).modifyTailIdx g (m - n)) n := by
+  rcases Nat.exists_eq_add_of_le h with ⟨m, rfl⟩
+  rw [Nat.add_comm, modifyTailIdx_modifyTailIdx, Nat.add_sub_cancel]
+
+theorem modifyTailIdx_modifyTailIdx_eq {f g : List α → List α} (n : Nat) (l : List α) :
+    (l.modifyTailIdx f n).modifyTailIdx g n = l.modifyTailIdx (g ∘ f) n := by
+  rw [modifyTailIdx_modifyTailIdx_le n n l (Nat.le_refl n), Nat.sub_self]; rfl
+
 /-! ### modify -/
 
 @[simp] theorem modify_nil (f : α → α) (n) : [].modify f n = [] := by cases n <;> rfl