diff --git src/wp-includes/query.php src/wp-includes/query.php
index 84eda07..9fae51a 100644
--- src/wp-includes/query.php
+++ src/wp-includes/query.php
@@ -820,6 +820,51 @@ function the_post() {
 	$wp_query->the_post();
 }
 
+/**
+ * Get the current post's position in the current query.
+ *
+ * @since 4.4.0
+ *
+ * @global WP_Query $wp_query Global WP_Query instance.
+ *
+ * @return int The current post's position (zero-based).
+ */
+function query_post_index() {
+	global $wp_query;
+
+	return $wp_query->current_post;
+}
+
+/**
+ * Whether the current post is the first one in the current query.
+ *
+ * @since 4.4.0
+ *
+ * @global WP_Query $wp_query Global WP_Query instance.
+ *
+ * @return bool True if it's the first post in the query, false otherwise.
+ */
+function query_is_first_post() {
+	global $wp_query;
+
+	return $wp_query->is_first_post();
+}
+
+/**
+ * Whether the current post is the last one in the current query.
+ *
+ * @since 4.4.0
+ *
+ * @global WP_Query $wp_query Global WP_Query instance.
+ *
+ * @return bool True if it's the last post in the query, false otherwise.
+ */
+function query_is_last_post() {
+	global $wp_query;
+
+	return $wp_query->is_last_post();
+}
+
 /*
  * Comments loop.
  */
@@ -3874,6 +3919,28 @@ class WP_Query {
 	}
 
 	/**
+	 * Whether the current post is the first one in the query.
+	 *
+	 * @since 4.4.0
+	 *
+	 * @return bool True if it's the first post in the query, false otherwise.
+	 */
+	function is_first_post() {
+		return 0 === $this->current_post;
+	}
+
+	/**
+	 * Whether the current post is the last one in the current query.
+	 *
+	 * @since 4.4.0
+	 *
+	 * @return bool True if it's the last post in the query, false otherwise.
+	 */
+	function is_last_post() {
+		return $this->current_post === ( $this->post_count - 1 );
+	}
+
+	/**
 	 * Whether there are more posts available in the loop.
 	 *
 	 * Calls action 'loop_end', when the loop is complete.
diff --git tests/phpunit/tests/query.php tests/phpunit/tests/query.php
index b5ab027..6e46eca 100644
--- tests/phpunit/tests/query.php
+++ tests/phpunit/tests/query.php
@@ -492,4 +492,91 @@ class Tests_Query extends WP_UnitTestCase {
 
 		$this->assertContains( 'LIMIT 5, 5', $q->request );
 	}
+
+	/**
+	 * @ticket 33860
+	 */
+	public function test_query_index() {
+		self::factory()->post->create_many( 5 );
+
+		$q = new WP_Query( array(
+			'posts_per_page' => -1,
+		) );
+
+		$actual = array();
+		$index = array();
+
+		while ( $q->have_posts() ) {
+			$q->the_post();
+
+			$index[] = $q->current_post;
+
+			if ( $q->is_first_post() ) {
+				$actual[] = 'first';
+			} else if ( $q->is_last_post() ) {
+				$actual[] = 'last';
+			} else {
+				$actual[] = 'middle';
+			}
+		}
+
+		$this->assertSame( array( 'first', 'middle', 'middle', 'middle', 'last' ), $actual );
+		$this->assertSame( array( 0, 1, 2, 3, 4 ), $index );
+	}
+
+	/**
+	 * @ticket 33860
+	 */
+	public function test_query_index_paginated() {
+		self::factory()->post->create_many( 4 );
+
+		$q = new WP_Query( array(
+			'posts_per_page' => 2,
+		) );
+
+		$actual = array();
+		$index = array();
+
+		while ( $q->have_posts() ) {
+			$q->the_post();
+
+			$index[] = $q->current_post;
+
+			if ( $q->is_first_post() ) {
+				$actual[] = 'first';
+			} else if ( $q->is_last_post() ) {
+				$actual[] = 'last';
+			} else {
+				$actual[] = 'middle';
+			}
+		}
+
+		$this->assertSame( array( 'first', 'last' ), $actual );
+		$this->assertSame( array( 0, 1 ), $index );
+
+		$q = new WP_Query( array(
+			'posts_per_page' => 2,
+			'paged'          => 2,
+		) );
+
+		$actual = array();
+		$index = array();
+
+		while ( $q->have_posts() ) {
+			$q->the_post();
+
+			$index[] = $q->current_post;
+
+			if ( $q->is_first_post() ) {
+				$actual[] = 'first';
+			} else if ( $q->is_last_post() ) {
+				$actual[] = 'last';
+			} else {
+				$actual[] = 'middle';
+			}
+		}
+
+		$this->assertSame( array( 'first', 'last' ), $actual );
+		$this->assertSame( array( 0, 1 ), $index );
+	}
 }
