diff --git a/examples/client.cc b/examples/client.cc
index 81647de6d1c9874b5f47935abb9024158e29f023..b7c9eb9830b370d22ef7f157a9a1ca31a7c77cbc 100644
--- a/examples/client.cc
+++ b/examples/client.cc
@@ -220,18 +220,17 @@ void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
 namespace {
 void retransmitcb(struct ev_loop *loop, ev_timer *w, int revents) {
   auto c = static_cast<Client *>(w->data);
+  auto conn = c->conn();
+  auto now = util::timestamp();
 
-  if (c->on_write(true) != 0) {
+  if (ngtcp2_conn_earliest_expiry(conn) < now + 1000000 &&
+      c->on_write(true) != 0) {
     c->disconnect();
+    return;
   }
-}
-} // namespace
 
-namespace {
-void ackcb(struct ev_loop *loop, ev_timer *w, int revents) {
-  auto c = static_cast<Client *>(w->data);
-
-  if (c->on_write() != 0) {
+  if (ngtcp2_conn_ack_delay_expiry(conn) < now + 1000000 &&
+      c->on_write() != 0) {
     c->disconnect();
   }
 }
@@ -269,8 +268,6 @@ Client::Client(struct ev_loop *loop, SSL_CTX *ssl_ctx)
   timer_.data = this;
   ev_timer_init(&rttimer_, retransmitcb, 0., 0.);
   rttimer_.data = this;
-  ev_timer_init(&acktimer_, ackcb, 0., 0.);
-  acktimer_.data = this;
   ev_signal_init(&sigintev_, siginthandler, SIGINT);
 }
 
@@ -284,7 +281,6 @@ void Client::disconnect() { disconnect(0); }
 void Client::disconnect(int liberr) {
   config.tx_loss_prob = 0;
 
-  ev_timer_stop(loop_, &acktimer_);
   ev_timer_stop(loop_, &rttimer_);
   ev_timer_stop(loop_, &timer_);
 
@@ -998,6 +994,9 @@ int Client::on_write(bool retransmit) {
         return rv;
       }
     }
+
+    schedule_retransmit();
+    return 0;
   }
 
   if (!ngtcp2_conn_get_handshake_completed(conn_)) {
@@ -1116,33 +1115,24 @@ void Client::schedule_retransmit() {
   ev_tstamp t;
   auto now = util::timestamp();
 
-  auto expiry = ngtcp2_conn_earliest_expiry(conn_);
-  if (expiry) {
-    ev_timer_stop(loop_, &rttimer_);
+  auto expiry = std::min(ngtcp2_conn_earliest_expiry(conn_),
+                         ngtcp2_conn_ack_delay_expiry(conn_));
 
-    if (now >= expiry) {
-      t = 0.;
-    } else {
-      t = static_cast<ev_tstamp>(expiry - now) / 1000000000;
-    }
+  if (expiry == UINT64_MAX) {
     ev_timer_stop(loop_, &rttimer_);
-    ev_timer_set(&rttimer_, t, 0.);
-    ev_timer_start(loop_, &rttimer_);
+    return;
   }
 
-  expiry = ngtcp2_conn_ack_delay_expiry(conn_);
-  if (expiry) {
-    ev_timer_stop(loop_, &acktimer_);
+  ev_timer_stop(loop_, &rttimer_);
 
-    if (now >= expiry) {
-      t = 0.;
-    } else {
-      t = static_cast<ev_tstamp>(expiry - now) / 1000000000;
-    }
-    ev_timer_stop(loop_, &acktimer_);
-    ev_timer_set(&acktimer_, t, 0.);
-    ev_timer_start(loop_, &acktimer_);
+  if (now >= expiry) {
+    t = 0.;
+  } else {
+    t = static_cast<ev_tstamp>(expiry - now) / 1000000000;
   }
+  ev_timer_stop(loop_, &rttimer_);
+  ev_timer_set(&rttimer_, t, 0.);
+  ev_timer_start(loop_, &rttimer_);
 }
 
 int Client::write_client_handshake(const uint8_t *data, size_t datalen) {
diff --git a/examples/client.h b/examples/client.h
index c75e8987b3db004e3fba778bb0caacbf61045341..d5f24b69e72232376148cb3f93332f43135f06d5 100644
--- a/examples/client.h
+++ b/examples/client.h
@@ -199,7 +199,6 @@ private:
   ev_io stdinrev_;
   ev_timer timer_;
   ev_timer rttimer_;
-  ev_timer acktimer_;
   ev_signal sigintev_;
   struct ev_loop *loop_;
   SSL_CTX *ssl_ctx_;
diff --git a/examples/server.cc b/examples/server.cc
index 6c097d93b231bb03a5266a122e908e98fe63d045..ee798e14a81ebfc0872b39730275353f7fc61ca3 100644
--- a/examples/server.cc
+++ b/examples/server.cc
@@ -500,17 +500,37 @@ void retransmitcb(struct ev_loop *loop, ev_timer *w, int revents) {
 
   auto h = static_cast<Handler *>(w->data);
   auto s = h->server();
+  auto conn = h->conn();
+  auto now = util::timestamp();
 
-  rv = h->on_write(true);
-  switch (rv) {
-  case 0:
-  case NETWORK_ERR_CLOSE_WAIT:
-    break;
-  case NETWORK_ERR_SEND_NON_FATAL:
-    s->start_wev();
-    break;
-  default:
-    s->remove(h);
+  if (ngtcp2_conn_earliest_expiry(conn) < now + 1000000) {
+    rv = h->on_write(true);
+    switch (rv) {
+    case 0:
+    case NETWORK_ERR_CLOSE_WAIT:
+      return;
+    case NETWORK_ERR_SEND_NON_FATAL:
+      s->start_wev();
+      return;
+    default:
+      s->remove(h);
+      return;
+    }
+  }
+
+  if (ngtcp2_conn_ack_delay_expiry(conn) < now + 1000000) {
+    rv = h->on_write();
+    switch (rv) {
+    case 0:
+    case NETWORK_ERR_CLOSE_WAIT:
+      return;
+    case NETWORK_ERR_SEND_NON_FATAL:
+      s->start_wev();
+      return;
+    default:
+      s->remove(h);
+      return;
+    }
   }
 }
 } // namespace
@@ -1362,6 +1382,9 @@ int Handler::on_write(bool retransmit) {
         return rv;
       }
     }
+
+    schedule_retransmit();
+    return 0;
   }
 
   if (!ngtcp2_conn_get_handshake_completed(conn_)) {
@@ -1567,8 +1590,9 @@ int Handler::send_conn_close() {
 }
 
 void Handler::schedule_retransmit() {
-  auto expiry = ngtcp2_conn_earliest_expiry(conn_);
-  if (expiry == 0) {
+  auto expiry = std::min(ngtcp2_conn_earliest_expiry(conn_),
+                         ngtcp2_conn_ack_delay_expiry(conn_));
+  if (expiry == UINT64_MAX) {
     ev_timer_stop(loop_, &rttimer_);
     return;
   }
diff --git a/lib/ngtcp2_acktr.c b/lib/ngtcp2_acktr.c
index 02e0f4515e989881a58d702ed28ec19510d9696b..dc821330723d7256747f2437f2a8970e083a9129 100644
--- a/lib/ngtcp2_acktr.c
+++ b/lib/ngtcp2_acktr.c
@@ -66,8 +66,7 @@ int ngtcp2_acktr_init(ngtcp2_acktr *acktr, ngtcp2_mem *mem) {
   acktr->nack = 0;
   acktr->last_hs_ack_pkt_num = UINT64_MAX;
   acktr->flags = NGTCP2_ACKTR_FLAG_NONE;
-  acktr->last_unprotected_added = 0;
-  acktr->last_added = 0;
+  acktr->first_unacked_ts = 0;
 
   return 0;
 }
@@ -123,17 +122,11 @@ int ngtcp2_acktr_add(ngtcp2_acktr *acktr, ngtcp2_acktr_entry *ent,
     if (ent->unprotected) {
       /* Should be sent in both protected and unprotected ACK */
       acktr->flags |= NGTCP2_ACKTR_FLAG_ACTIVE_ACK;
-      if (!acktr->last_unprotected_added) {
-        acktr->last_unprotected_added = ts;
-      }
-      if (!acktr->last_added) {
-        acktr->last_added = ts;
-      }
     } else {
       acktr->flags |= NGTCP2_ACKTR_FLAG_ACTIVE_ACK_PROTECTED;
-      if (!acktr->last_added) {
-        acktr->last_added = ts;
-      }
+    }
+    if (acktr->first_unacked_ts == UINT64_MAX) {
+      acktr->first_unacked_ts = ts;
     }
   }
 
@@ -363,20 +356,17 @@ int ngtcp2_acktr_recv_ack(ngtcp2_acktr *acktr, const ngtcp2_ack *fr,
 void ngtcp2_acktr_commit_ack(ngtcp2_acktr *acktr, int unprotected) {
   if (unprotected) {
     acktr->flags &= (uint8_t)~NGTCP2_ACKTR_FLAG_ACTIVE_ACK_UNPROTECTED;
-    acktr->last_unprotected_added = 0;
   } else {
     acktr->flags &= (uint8_t)~NGTCP2_ACKTR_FLAG_ACTIVE_ACK_PROTECTED;
-    acktr->last_added = 0;
+    acktr->first_unacked_ts = UINT64_MAX;
   }
 }
 
 int ngtcp2_acktr_require_active_ack(ngtcp2_acktr *acktr, int unprotected,
                                     uint64_t max_ack_delay, ngtcp2_tstamp ts) {
   if (unprotected) {
-    return (acktr->flags & NGTCP2_ACKTR_FLAG_ACTIVE_ACK_UNPROTECTED) &&
-           acktr->last_unprotected_added &&
-           acktr->last_unprotected_added + max_ack_delay <= ts;
+    return acktr->flags & NGTCP2_ACKTR_FLAG_ACTIVE_ACK_UNPROTECTED;
   }
   return (acktr->flags & NGTCP2_ACKTR_FLAG_ACTIVE_ACK_PROTECTED) &&
-         acktr->last_added && acktr->last_added + max_ack_delay <= ts;
+         acktr->first_unacked_ts <= ts - max_ack_delay;
 }
diff --git a/lib/ngtcp2_acktr.h b/lib/ngtcp2_acktr.h
index b6c3f3d7836059911f18808c8963e817dfbf4c8b..06ecac25c0bd0a62b7628bcd3c4bcf9b45a5e9d5 100644
--- a/lib/ngtcp2_acktr.h
+++ b/lib/ngtcp2_acktr.h
@@ -117,15 +117,9 @@ typedef struct {
   uint64_t last_hs_ack_pkt_num;
   /* flags is bitwise OR of zero, or more of ngtcp2_ack_flag. */
   uint8_t flags;
-  /* last_unprotected_added is timestamp when unprotected
-     ngtcp2_acktr_entry is added first time after the last outgoing
-     ACK frame. */
-  /* TODO This should be first_unprotected_unacked_ts */
-  ngtcp2_tstamp last_unprotected_added;
-  /* last_added is timestamp when ngtcp2_acktr_entry is added first
-     time after the last outgoing ACK frame. */
-  /* TODO This should be first_unacked_ts */
-  ngtcp2_tstamp last_added;
+  /* first_unacked_ts is timestamp when ngtcp2_acktr_entry is added
+     first time after the last outgoing protected ACK frame. */
+  ngtcp2_tstamp first_unacked_ts;
 } ngtcp2_acktr;
 
 /*
diff --git a/lib/ngtcp2_conn.c b/lib/ngtcp2_conn.c
index 4cf137947fa456ab85481a236029a817fb49b5cb..ff7faf798040798541214c3b6f9516c1ebc68e5b 100644
--- a/lib/ngtcp2_conn.c
+++ b/lib/ngtcp2_conn.c
@@ -373,6 +373,22 @@ static int conn_ensure_ack_blks(ngtcp2_conn *conn, ngtcp2_frame **pfr,
   return 0;
 }
 
+/*
+ * conn_compute_ack_delay computes ACK delay for outgoing protected
+ * ACK.
+ */
+static uint64_t conn_compute_ack_delay(ngtcp2_conn *conn) {
+  uint64_t ack_delay;
+
+  if (conn->rcs.min_rtt == 0) {
+    return NGTCP2_DEFAULT_ACK_DELAY;
+  }
+
+  ack_delay = (uint64_t)(conn->rcs.smoothed_rtt / 4);
+
+  return ngtcp2_min(NGTCP2_DEFAULT_ACK_DELAY, ack_delay);
+}
+
 /*
  * conn_create_ack_frame creates ACK frame, and assigns its pointer to
  * |*pfr| if there are any received packets to acknowledge.  If there
@@ -407,12 +423,7 @@ static int conn_create_ack_frame(ngtcp2_conn *conn, ngtcp2_frame **pfr,
   size_t num_blks_max = 8;
   size_t blk_idx;
   int rv;
-  uint64_t max_ack_delay = NGTCP2_DEFAULT_ACK_DELAY;
-
-  if (conn->rcs.min_rtt) {
-    max_ack_delay =
-        ngtcp2_min(max_ack_delay, (uint64_t)(conn->rcs.smoothed_rtt / 4));
-  }
+  uint64_t max_ack_delay = conn_compute_ack_delay(conn);
 
   if (!ngtcp2_acktr_require_active_ack(&conn->acktr, unprotected, max_ack_delay,
                                        ts)) {
@@ -425,9 +436,8 @@ static int conn_create_ack_frame(ngtcp2_conn *conn, ngtcp2_frame **pfr,
       ;
   }
   if (*prpkt == NULL) {
-    conn->acktr.last_unprotected_added = 0;
     if (!unprotected) {
-      conn->acktr.last_added = 0;
+      conn->acktr.first_unacked_ts = UINT64_MAX;
     }
     return 0;
   }
@@ -4035,31 +4045,17 @@ int ngtcp2_conn_update_rx_keys(ngtcp2_conn *conn, const uint8_t *key,
 }
 
 ngtcp2_tstamp ngtcp2_conn_earliest_expiry(ngtcp2_conn *conn) {
-  return conn->rcs.loss_detection_alarm;
+  if (conn->rcs.loss_detection_alarm) {
+    return conn->rcs.loss_detection_alarm;
+  }
+  return UINT64_MAX;
 }
 
 ngtcp2_tstamp ngtcp2_conn_ack_delay_expiry(ngtcp2_conn *conn) {
-  if (ngtcp2_conn_get_handshake_completed(conn)) {
-    if (!conn->acktr.last_added) {
-      return 0;
-    }
-    return conn->acktr.last_added + NGTCP2_DEFAULT_ACK_DELAY;
-  }
-
-  if (!conn->acktr.last_added) {
-    if (!conn->acktr.last_unprotected_added) {
-      return 0;
-    }
-    return conn->acktr.last_unprotected_added + NGTCP2_DEFAULT_ACK_DELAY;
+  if (conn->acktr.first_unacked_ts == UINT64_MAX) {
+    return UINT64_MAX;
   }
-
-  if (!conn->acktr.last_unprotected_added) {
-    return conn->acktr.last_added + NGTCP2_DEFAULT_ACK_DELAY;
-  }
-
-  return ngtcp2_min(conn->acktr.last_added,
-                    conn->acktr.last_unprotected_added) +
-         NGTCP2_DEFAULT_ACK_DELAY;
+  return conn->acktr.first_unacked_ts + conn_compute_ack_delay(conn);
 }
 
 int ngtcp2_pkt_chain_new(ngtcp2_pkt_chain **ppc, const uint8_t *pkt,