diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php
index ede1330251..748efa5181 100644
--- a/src/wp-includes/user.php
+++ b/src/wp-includes/user.php
@@ -604,9 +604,19 @@ function wp_validate_logged_in_cookie( $user_id ) {
 function count_user_posts( $userid, $post_type = 'post', $public_only = false ) {
 	global $wpdb;
 
-	$where = get_posts_by_author_sql( $post_type, true, $userid, $public_only );
+	$post_type = array_unique( (array) $post_type );
+	sort( $post_type );
 
-	$count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts $where" );
+	$where = get_posts_by_author_sql( $post_type, true, $userid, $public_only );
+	$query = "SELECT COUNT(*) FROM $wpdb->posts $where";
+
+	$last_changed = wp_cache_get_last_changed( 'posts' );
+	$cache_key    = 'count_user_posts:' . md5( $query ) . ':' . $last_changed;
+	$count        = wp_cache_get( $cache_key, 'post-queries' );
+	if ( false === $count ) {
+		$count = $wpdb->get_var( $query );
+		wp_cache_set( $cache_key, $count, 'post-queries' );
+	}
 
 	/**
 	 * Filters the number of posts a user has written.
diff --git a/tests/phpunit/tests/user/countUserPosts.php b/tests/phpunit/tests/user/countUserPosts.php
index dbc94f418e..64b06adda2 100644
--- a/tests/phpunit/tests/user/countUserPosts.php
+++ b/tests/phpunit/tests/user/countUserPosts.php
@@ -89,4 +89,142 @@ class Tests_User_CountUserPosts extends WP_UnitTestCase {
 	public function test_count_user_posts_should_ignore_non_existent_post_types() {
 		$this->assertSame( '4', count_user_posts( self::$user_id, array( 'foo', 'post' ) ) );
 	}
+
+	/**
+	 * Post count be correct after reassigning posts to another user.
+	 *
+	 * @ticket 39242
+	 */
+	public function test_reassigning_users_posts_modifies_count() {
+		// Create new user.
+		$new_user_id = self::factory()->user->create(
+			array(
+				'role' => 'author',
+			)
+		);
+
+		// Prior to reassigning posts.
+		$this->assertSame( '4', count_user_posts( self::$user_id ), 'Original user is expected to have a count of four posts prior to reassignment.' );
+		$this->assertSame( '0', count_user_posts( $new_user_id ), 'New user is expected to have a count of zero posts prior to reassignment.' );
+
+		// Delete the original user, reassigning their posts to the new user.
+		wp_delete_user( self::$user_id, $new_user_id );
+
+		// After reassigning posts.
+		$this->assertSame( '0', count_user_posts( self::$user_id ), 'Original user is expected to have a count of zero posts following reassignment.' );
+		$this->assertSame( '4', count_user_posts( $new_user_id ), 'New user is expected to have a count of four posts following reassignment.' );
+	}
+
+	/**
+	 * Post count be correct after deleting user without reassigning posts.
+	 *
+	 * @ticket 39242
+	 */
+	public function test_post_count_retained_after_deleting_user_without_reassigning_posts() {
+		$this->assertSame( '4', count_user_posts( self::$user_id ), 'User is expected to have a count of four posts prior to deletion.' );
+
+		// Delete the original user without reassigning their posts.
+		wp_delete_user( self::$user_id );
+
+		$this->assertSame( '0', count_user_posts( self::$user_id ), 'User is expected to have a count of zero posts following deletion.' );
+	}
+
+	/**
+	 * Post count should work for users that don't exist but have posts assigned.
+	 *
+	 * @ticket 39242
+	 */
+	public function test_count_user_posts_for_non_existent_user() {
+		$next_user_id = self::$user_id + 1;
+
+		// Assign post to next user.
+		self::factory()->post->create(
+			array(
+				'post_author' => $next_user_id,
+				'post_type'   => 'post',
+			)
+		);
+
+		$next_user_post_count = count_user_posts( $next_user_id );
+		$this->assertSame( '1', $next_user_post_count, 'Non-existent user is expected to have count of one post.' );
+	}
+
+	/**
+	 * Cached user count value should be accurate after user is created.
+	 *
+	 * @ticket 39242
+	 */
+	public function test_count_user_posts_for_user_created_after_being_assigned_posts() {
+		global $wpdb;
+		$next_user_id = (int) $wpdb->get_var( "SELECT `auto_increment` FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '$wpdb->users'" );
+
+		// Assign post to next user.
+		self::factory()->post->create(
+			array(
+				'post_author' => $next_user_id,
+				'post_type'   => 'post',
+			)
+		);
+
+		// Cache the user count.
+		count_user_posts( $next_user_id );
+
+		// Create user.
+		$real_next_user_id = self::factory()->user->create(
+			array(
+				'role' => 'author',
+			)
+		);
+
+		$this->assertSame( $next_user_id, $real_next_user_id, 'User ID should match calculated value' );
+		$this->assertSame( '1', count_user_posts( $next_user_id ), 'User is expected to have count of one post.' );
+	}
+
+	/**
+	 * User count cache should be hit regardless of post type order.
+	 *
+	 * @ticket 39242
+	 */
+	public function test_cache_should_be_hit_regardless_of_post_type_order() {
+		// Prime Cache.
+		count_user_posts( self::$user_id, array( 'wptests_pt', 'post' ) );
+
+		$query_num_start = get_num_queries();
+		count_user_posts( self::$user_id, array( 'post', 'wptests_pt' ) );
+		$total_queries = get_num_queries() - $query_num_start;
+
+		$this->assertSame( 0, $total_queries, 'Cache should be hit regardless of post type order.' );
+	}
+
+	/**
+	 * User count cache should be hit for string and array of post types.
+	 *
+	 * @ticket 39242
+	 */
+	public function test_cache_should_be_hit_for_string_and_array_equivalent_queries() {
+		// Prime Cache.
+		count_user_posts( self::$user_id, 'post' );
+
+		$query_num_start = get_num_queries();
+		count_user_posts( self::$user_id, array( 'post' ) );
+		$total_queries = get_num_queries() - $query_num_start;
+
+		$this->assertSame( 0, $total_queries, 'Cache should be hit for string and array equivalent post types.' );
+	}
+
+	/**
+	 * User count cache should be hit for array duplicates and equivalent queries.
+	 *
+	 * @ticket 39242
+	*/
+	public function test_cache_should_be_hit_for_and_array_duplicates_equivalent_queries() {
+		// Prime Cache
+		count_user_posts( self::$user_id, array( 'post', 'post', 'post' ) );
+
+		$query_num_start = get_num_queries();
+		count_user_posts( self::$user_id, array( 'post' ) );
+		$total_queries = get_num_queries() - $query_num_start;
+
+		$this->assertSame( 0, $total_queries, 'Cache is expected to be hit for equivalent queries with duplicate post types' );
+	}
 }