diff --git a/lib/ngtcp2_rtb.c b/lib/ngtcp2_rtb.c
index 0f1d97f27d4964e66d00f018771de4084593266d..44792bff9290ba667875422a2e57b249a75049b8 100644
--- a/lib/ngtcp2_rtb.c
+++ b/lib/ngtcp2_rtb.c
@@ -241,6 +241,7 @@ void ngtcp2_rtb_init(ngtcp2_rtb *rtb, ngtcp2_pktns_id pktns_id,
   rtb->cc_pkt_num = 0;
   rtb->cc_bytes_in_flight = 0;
   rtb->persistent_congestion_start_ts = UINT64_MAX;
+  rtb->num_lost_pkts = 0;
 }
 
 void ngtcp2_rtb_free(ngtcp2_rtb *rtb) {
@@ -322,6 +323,8 @@ static int rtb_on_pkt_lost(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc,
       ent->flags |= NGTCP2_RTB_FLAG_LOST_RETRANSMITTED;
       ent->lost_ts = ts;
 
+      ++rtb->num_lost_pkts;
+
       ngtcp2_ksl_it_next(it);
 
       return 0;
@@ -431,6 +434,8 @@ static int rtb_on_pkt_lost(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc,
         ent->flags |= NGTCP2_RTB_FLAG_LOST_RETRANSMITTED;
         ent->lost_ts = ts;
 
+        ++rtb->num_lost_pkts;
+
         ngtcp2_ksl_it_next(it);
 
         return 0;
@@ -720,12 +725,8 @@ ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr,
 
 static int rtb_pkt_lost(ngtcp2_rtb *rtb, ngtcp2_conn_stat *cstat,
                         const ngtcp2_rtb_entry *ent, uint64_t loss_delay,
-                        ngtcp2_tstamp lost_send_time) {
+                        ngtcp2_tstamp lost_send_time, uint64_t pkt_thres) {
   ngtcp2_tstamp loss_time;
-  uint64_t pkt_thres =
-      rtb->cc_bytes_in_flight / cstat->max_udp_payload_size / 2;
-
-  pkt_thres = ngtcp2_max(pkt_thres, NGTCP2_PKT_THRESHOLD);
 
   if (ent->ts <= lost_send_time ||
       rtb->largest_acked_tx_pkt_num >= ent->hd.pkt_num + (int64_t)pkt_thres) {
@@ -768,7 +769,10 @@ int ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc,
   ngtcp2_duration loss_window, congestion_period;
   ngtcp2_cc *cc = rtb->cc;
   int rv;
+  uint64_t pkt_thres =
+      rtb->cc_bytes_in_flight / cstat->max_udp_payload_size / 2;
 
+  pkt_thres = ngtcp2_max(pkt_thres, NGTCP2_PKT_THRESHOLD);
   cstat->loss_time[rtb->pktns_id] = UINT64_MAX;
   loss_delay = compute_pkt_loss_delay(cstat);
   lost_send_time = ts - loss_delay;
@@ -781,7 +785,7 @@ int ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc,
       break;
     }
 
-    if (rtb_pkt_lost(rtb, cstat, ent, loss_delay, lost_send_time)) {
+    if (rtb_pkt_lost(rtb, cstat, ent, loss_delay, lost_send_time, pkt_thres)) {
       /* All entries from ent are considered to be lost. */
       latest_ts = oldest_ts = ent->ts;
       last_lost_pkt_num = ent->hd.pkt_num;
@@ -840,12 +844,31 @@ int ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc,
     }
   }
 
-  /* TODO Limit the number of LOST_RETRANSMITTED entries under maximum
-     reordering threshold. */
+  ngtcp2_rtb_remove_excessive_lost_pkt(rtb, pkt_thres);
 
   return 0;
 }
 
+void ngtcp2_rtb_remove_excessive_lost_pkt(ngtcp2_rtb *rtb, size_t n) {
+  ngtcp2_ksl_it it = ngtcp2_ksl_end(&rtb->ents);
+  ngtcp2_rtb_entry *ent;
+
+  for (; rtb->num_lost_pkts > n;) {
+    assert(ngtcp2_ksl_it_end(&it));
+    ngtcp2_ksl_it_prev(&it);
+    ent = ngtcp2_ksl_it_get(&it);
+
+    assert(ent->flags & NGTCP2_RTB_FLAG_LOST_RETRANSMITTED);
+
+    ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_RCV,
+                    "removing stale lost pkn=%" PRId64, ent->hd.pkt_num);
+
+    --rtb->num_lost_pkts;
+    ngtcp2_ksl_remove(&rtb->ents, &it, &ent->hd.pkt_num);
+    ngtcp2_rtb_entry_del(ent, rtb->mem);
+  }
+}
+
 void ngtcp2_rtb_remove_expired_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_duration pto,
                                         ngtcp2_tstamp ts) {
   ngtcp2_ksl_it it;
@@ -871,6 +894,7 @@ void ngtcp2_rtb_remove_expired_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_duration pto,
     ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_RCV,
                     "removing stale lost pkn=%" PRId64, ent->hd.pkt_num);
 
+    --rtb->num_lost_pkts;
     ngtcp2_ksl_remove(&rtb->ents, &it, &ent->hd.pkt_num);
     ngtcp2_rtb_entry_del(ent, rtb->mem);
 
@@ -909,6 +933,10 @@ static void rtb_on_pkt_lost2(ngtcp2_rtb *rtb, ngtcp2_frame_chain **pfrc,
   }
 
   if (!(ent->flags & NGTCP2_RTB_FLAG_PROBE)) {
+    if (ent->flags & NGTCP2_RTB_FLAG_LOST_RETRANSMITTED) {
+      --rtb->num_lost_pkts;
+    }
+
     if (ent->flags & NGTCP2_RTB_FLAG_CRYPTO_TIMEOUT_RETRANSMITTED) {
       ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_RCV,
                       "pkn=%" PRId64 " CRYPTO has already been retransmitted",
diff --git a/lib/ngtcp2_rtb.h b/lib/ngtcp2_rtb.h
index b9f350fa857accb081b62610259bab3aa60b7f04..d74b56f075b7603586d25aaaee2438bdff2523fa 100644
--- a/lib/ngtcp2_rtb.h
+++ b/lib/ngtcp2_rtb.h
@@ -259,6 +259,9 @@ typedef struct {
      congestion evaluation is started.  It happens roughly after
      handshake is confirmed. */
   ngtcp2_tstamp persistent_congestion_start_ts;
+  /* num_lost_pkts is the number entries in ents which has
+     NGTCP2_RTB_FLAG_LOST_RETRANSMITTED flag set. */
+  size_t num_lost_pkts;
 } ngtcp2_rtb;
 
 /*
@@ -368,4 +371,10 @@ int ngtcp2_rtb_empty(ngtcp2_rtb *rtb);
  */
 void ngtcp2_rtb_reset_cc_state(ngtcp2_rtb *rtb, int64_t cc_pkt_num);
 
+/*
+ * ngtcp2_rtb_remove_expired_lost_pkt ensures that the number of lost
+ * packets at most |n|.
+ */
+void ngtcp2_rtb_remove_excessive_lost_pkt(ngtcp2_rtb *rtb, size_t n);
+
 #endif /* NGTCP2_RTB_H */
diff --git a/tests/main.c b/tests/main.c
index 3ac72fc1c92a86ce46adf71c233bc6ac949b00b1..1336eb82ce0c38bbea5f0804b1ca0298e05dae5d 100644
--- a/tests/main.c
+++ b/tests/main.c
@@ -169,6 +169,8 @@ int main() {
       !CU_add_test(pSuite, "rtb_lost_pkt_ts", test_ngtcp2_rtb_lost_pkt_ts) ||
       !CU_add_test(pSuite, "rtb_remove_expired_lost_pkt",
                    test_ngtcp2_rtb_remove_expired_lost_pkt) ||
+      !CU_add_test(pSuite, "rtb_remove_excessive_lost_pkt",
+                   test_ngtcp2_rtb_remove_excessive_lost_pkt) ||
       !CU_add_test(pSuite, "idtr_open", test_ngtcp2_idtr_open) ||
       !CU_add_test(pSuite, "ringbuf_push_front",
                    test_ngtcp2_ringbuf_push_front) ||
diff --git a/tests/ngtcp2_rtb_test.c b/tests/ngtcp2_rtb_test.c
index 3e303da36d37ec19f287b46af316bef9ebf8ab97..3f6c4f132d86128018c9d0769d28bcdd2a80181a 100644
--- a/tests/ngtcp2_rtb_test.c
+++ b/tests/ngtcp2_rtb_test.c
@@ -367,3 +367,45 @@ void test_ngtcp2_rtb_remove_expired_lost_pkt(void) {
   ngtcp2_cc_reno_cc_free(&cc, mem);
   ngtcp2_strm_free(&crypto);
 }
+
+void test_ngtcp2_rtb_remove_excessive_lost_pkt(void) {
+  ngtcp2_rtb rtb;
+  const ngtcp2_pktns_id pktns_id = NGTCP2_PKTNS_ID_APP;
+  ngtcp2_strm crypto;
+  ngtcp2_log log;
+  const ngtcp2_mem *mem = ngtcp2_mem_default();
+  ngtcp2_cc cc;
+  ngtcp2_rst rst;
+  ngtcp2_conn_stat cstat;
+  ngtcp2_ksl_it it;
+  ngtcp2_rtb_entry *ent;
+  size_t i;
+
+  ngtcp2_strm_init(&crypto, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, mem);
+  ngtcp2_log_init(&log, NULL, NULL, 0, NULL);
+
+  conn_stat_init(&cstat);
+  ngtcp2_rst_init(&rst);
+  ngtcp2_cc_reno_cc_init(&cc, &log, mem);
+  ngtcp2_rtb_init(&rtb, pktns_id, &crypto, &rst, &cc, &log, NULL, mem);
+
+  add_rtb_entry_range(&rtb, 0, 7, &cstat, mem);
+
+  it = ngtcp2_ksl_end(&rtb.ents);
+
+  for (i = 0; i < 5; ++i) {
+    ngtcp2_ksl_it_prev(&it);
+    ent = ngtcp2_ksl_it_get(&it);
+    ent->flags |= NGTCP2_RTB_FLAG_LOST_RETRANSMITTED;
+    ent->lost_ts = 16777217;
+    ++rtb.num_lost_pkts;
+  }
+
+  ngtcp2_rtb_remove_excessive_lost_pkt(&rtb, 2);
+
+  CU_ASSERT(4 == ngtcp2_ksl_len(&rtb.ents));
+
+  ngtcp2_rtb_free(&rtb);
+  ngtcp2_cc_reno_cc_free(&cc, mem);
+  ngtcp2_strm_free(&crypto);
+}
diff --git a/tests/ngtcp2_rtb_test.h b/tests/ngtcp2_rtb_test.h
index c9bf2f9671973f3fbd34939a9922809896756ac1..f880e1eb786cbf748c27c570725ef3e5951fe55e 100644
--- a/tests/ngtcp2_rtb_test.h
+++ b/tests/ngtcp2_rtb_test.h
@@ -33,5 +33,6 @@ void test_ngtcp2_rtb_add(void);
 void test_ngtcp2_rtb_recv_ack(void);
 void test_ngtcp2_rtb_lost_pkt_ts(void);
 void test_ngtcp2_rtb_remove_expired_lost_pkt(void);
+void test_ngtcp2_rtb_remove_excessive_lost_pkt(void);
 
 #endif /* NGTCP2_RTB_TEST_H */