diff --git src/wp-includes/canonical.php src/wp-includes/canonical.php
index a88b3a8..52850b4 100644
--- src/wp-includes/canonical.php
+++ src/wp-includes/canonical.php
@@ -321,7 +321,7 @@ function redirect_canonical( $requested_url = null, $do_redirect = true ) {
 				}
 			}
 
-			if ( get_option('page_comments') && ( ( 'newest' == get_option('default_comments_page') && get_query_var('cpage') > 0 ) || ( 'newest' != get_option('default_comments_page') && get_query_var('cpage') > 1 ) ) ) {
+			if ( ( 'newest' == get_option('default_comments_page') && get_query_var('cpage') > 0 ) || ( 'newest' != get_option('default_comments_page') && get_query_var('cpage') > 1 ) ) {
 				$addl_path = ( !empty( $addl_path ) ? trailingslashit($addl_path) : '' ) . user_trailingslashit( $wp_rewrite->comments_pagination_base . '-' . get_query_var('cpage'), 'commentpaged' );
 				$redirect['query'] = remove_query_arg( 'cpage', $redirect['query'] );
 			}
diff --git src/wp-includes/class-wp-comment-query.php src/wp-includes/class-wp-comment-query.php
index 2e978c4..4050e48 100644
--- src/wp-includes/class-wp-comment-query.php
+++ src/wp-includes/class-wp-comment-query.php
@@ -865,6 +865,10 @@ class WP_Comment_Query {
 		$level = 0;
 		do {
 			$parent_ids = $levels[ $level ];
+			if ( ! $parent_ids ) {
+				break;
+			}
+
 			$where = 'WHERE ' . implode( ' AND ', $where_clauses ) . ' AND comment_parent IN (' . implode( ',', array_map( 'intval', $parent_ids ) ) . ')';
 			$comment_ids = $wpdb->get_col( "{$this->sql_clauses['select']} {$this->sql_clauses['from']} {$where} {$this->sql_clauses['groupby']}" );
 
diff --git src/wp-includes/class-wp-comment.php src/wp-includes/class-wp-comment.php
index 955925b..7225539 100644
--- src/wp-includes/class-wp-comment.php
+++ src/wp-includes/class-wp-comment.php
@@ -227,9 +227,11 @@ final class WP_Comment {
 	 * @since 4.4.0
 	 * @access public
 	 *
+	 * @param string $format Return value format. 'tree' for a hierarchical tree, 'flat' for a flattened array.
+	 *                       Default 'tree'.
 	 * @return array Array of `WP_Comment` objects.
 	 */
-	public function get_children() {
+	public function get_children( $format = 'tree' ) {
 		if ( is_null( $this->children ) ) {
 			$this->children = get_comments( array(
 				'parent' => $this->comment_ID,
@@ -237,7 +239,16 @@ final class WP_Comment {
 			) );
 		}
 
-		return $this->children;
+		if ( 'flat' === $format ) {
+			$children = array();
+			foreach ( $this->children as $child ) {
+				$children = array_merge( $children, array( $child ), $child->get_children( 'flat' ) );
+			}
+		} else {
+			$children = $this->children;
+		}
+
+		return $children;
 	}
 
 	/**
diff --git src/wp-includes/comment-template.php src/wp-includes/comment-template.php
index d8c16a9..8495cf3 100644
--- src/wp-includes/comment-template.php
+++ src/wp-includes/comment-template.php
@@ -1214,10 +1214,11 @@ function comments_template( $file = '/comments.php', $separate_comments = false
 	$comment_author_url = esc_url($commenter['comment_author_url']);
 
 	$comment_args = array(
-		'order'   => 'ASC',
 		'orderby' => 'comment_date_gmt',
 		'status'  => 'approve',
 		'post_id' => $post->ID,
+		'hierarchical' => 'threaded',
+		'no_found_rows' => false,
 		'update_comment_meta_cache' => false, // We lazy-load comment meta for performance.
 	);
 
@@ -1227,7 +1228,108 @@ function comments_template( $file = '/comments.php', $separate_comments = false
 		$comment_args['include_unapproved'] = array( $comment_author_email );
 	}
 
-	$comments = get_comments( $comment_args );
+	$per_page = (int) get_query_var( 'comments_per_page' );
+	if ( 0 === $per_page ) {
+		$per_page = (int) get_option( 'comments_per_page' );
+	}
+
+	$paging_threshold = $per_page ? $per_page * 2 : 100;
+
+	/**
+	 * Filter the threshold at which comment pagination is forced.
+	 *
+	 * Pages with large numbers of comments perform extremely poorly, so we force pagination when the comment
+	 * count exceeds a certain threshold. Use this filter to change that threshold.
+	 *
+	 * @since 4.4.0
+	 *
+	 * @param int     $paging_threshold Number of comments a post must have before pagination is forced.
+	 * @param WP_Post $post             The WP_Post instance.
+	 */
+	$paging_threshold = apply_filters( 'force_comment_paging_threshold', $paging_threshold, $post );
+
+	$flip_comment_order = $trim_comments_on_page = false;
+	if ( (int) get_option( 'page_comments' ) || $post->comment_count > $paging_threshold ) {
+		// Force pagination to be turned on in theme comment navigation functions.
+		add_filter( 'pre_option_page_comments', '__return_true' );
+
+		if ( ! $per_page ) {
+			$comment_args['number'] = $paging_threshold / 2;
+		}
+		$comment_args['number'] = $per_page;
+
+		/*
+		 * For legacy reasons, higher page numbers always mean more recent comments, regardless of sort order.
+		 * Since we don't have full pagination info until after the query, we use some tricks to get the
+		 * right comments for the current page.
+		 *
+		 * Abandon all hope, ye who enter here!
+		 */
+		$page = (int) get_query_var( 'cpage' );
+		if ( 'newest' === get_option( 'default_comments_page' ) ) {
+			if ( $page ) {
+				$comment_args['order'] = 'ASC';
+
+				/*
+				 * We don't have enough data (namely, the total number of comments) to calculate an
+				 * exact offset. We'll fetch too many comments, and trim them as needed
+				 * after the query.
+				 */
+				$offset = ( $page - 2 ) * $per_page;
+				if ( 0 > $offset ) {
+					// `WP_Comment_Query` doesn't support negative offsets.
+					$comment_args['offset'] = 0;
+				} else {
+					$comment_args['offset'] = $offset;
+				}
+
+				// Fetch double the number of comments we need.
+				$comment_args['number'] += $per_page;
+				$trim_comments_on_page = true;
+			} else {
+				$comment_args['order'] = 'DESC';
+				$comment_args['offset'] = 0;
+				$flip_comment_order = true;
+			}
+		} else {
+			$comment_args['order'] = 'ASC';
+			if ( $page ) {
+				$comment_args['offset'] = ( $page - 1 ) * $per_page;
+			} else {
+				$comment_args['offset'] = 0;
+			}
+		}
+	}
+
+	$comment_query = new WP_Comment_Query( $comment_args );
+	$_comments = $comment_query->comments;
+
+	// Delightful pagination quirk #1: first page of results sometimes needs reordering.
+	if ( $flip_comment_order ) {
+		$_comments = array_reverse( $_comments );
+	}
+
+	// Delightful pagination quirk #2: reverse chronological order requires page shifting.
+	if ( $trim_comments_on_page ) {
+		// Correct the value of max_num_pages, which is wrong because we manipulated the per_page 'number'.
+		$comment_query->max_num_pages = ceil( $comment_query->found_comments / $per_page );
+
+		// Identify the number of comments that should appear on page 1.
+		$page_1_count = $comment_query->found_comments - ( ( $comment_query->max_num_pages - 1 ) * $per_page );
+
+		// Use that value to shift the matched comments.
+		if ( 1 === $page ) {
+			$_comments = array_slice( $_comments, 0, $page_1_count );
+		} else {
+			$_comments = array_slice( $_comments, $page_1_count, $per_page );
+		}
+	}
+
+	// Trees must be flattened before they're passed to the walker.
+	$comments_flat = array();
+	foreach ( $_comments as $_comment ) {
+		$comments_flat = array_merge( $comments_flat, array( $_comment ), $_comment->get_children( 'flat' ) );
+	}
 
 	/**
 	 * Filter the comments array.
@@ -1237,9 +1339,10 @@ function comments_template( $file = '/comments.php', $separate_comments = false
 	 * @param array $comments Array of comments supplied to the comments template.
 	 * @param int   $post_ID  Post ID.
 	 */
-	$wp_query->comments = apply_filters( 'comments_array', $comments, $post->ID );
+	$wp_query->comments = apply_filters( 'comments_array', $comments_flat, $post->ID );
 	$comments = &$wp_query->comments;
 	$wp_query->comment_count = count($wp_query->comments);
+	$wp_query->max_num_comment_pages = $comment_query->max_num_pages;
 
 	if ( $separate_comments ) {
 		$wp_query->comments_by_type = separate_comments($comments);
@@ -1825,6 +1928,11 @@ function wp_list_comments( $args = array(), $comments = null ) {
 		} else {
 			$_comments = $wp_query->comments;
 		}
+
+		// Pagination is already handled by `WP_Comment_Query`, so we tell Walker not to bother.
+		if ( 1 < $wp_query->max_num_comment_pages ) {
+			$r['page'] = 1;
+		}
 	}
 
 	if ( '' === $r['per_page'] && get_option('page_comments') )
@@ -1866,7 +1974,6 @@ function wp_list_comments( $args = array(), $comments = null ) {
 	}
 
 	$output = $walker->paged_walk( $_comments, $r['max_depth'], $r['page'], $r['per_page'], $r );
-	$wp_query->max_num_comment_pages = $walker->max_pages;
 
 	$in_comment_loop = false;
 
