The Loop & WordPress Core Functions

WordPress Intermediate/Advanced

Chapter 3 · The Loop & WordPress Core Functions

WordPress Intermediate/Advanced 2 deliberately left the_content() unused because it needs something not yet introduced: the Loop. Every template file in Chapters 1 and 2 has been outputting a single post's data implicitly — this chapter formalizes exactly how, and how to query for posts beyond whatever WordPress already queried automatically.

The Loop, Formalized

The Loop is genuinely just a PHP while loop, exactly the kind of construct PHP Fundamentals already covers, wrapped around two WordPress-specific functions:

<?php if ( have_posts() ) : ?> <?php while ( have_posts() ) : the_post(); ?> <h2><?php the_title(); ?></h2> <?php the_content(); ?> <?php endwhile; ?> <?php endif; ?>
  • have_posts() — returns true while there are still posts remaining in the current query to loop through, exactly what any ordinary PHP while condition needs
  • the_post() — advances to the next post and sets up the global post data every template tag (the_title(), the_content(), the_permalink()) reads from, so each iteration's template tags correctly refer to that iteration's own post

Where "The Current Query" Actually Comes From

Before your template file ever runs, WordPress has already built a query based on the requested URL — visiting a category archive automatically queries that category's posts, visiting a single post automatically queries just that one post. This automatic query is why have_posts()/the_post() simply work in a single/archive template with no query-writing required — the query already happened.

WP_Query — Querying for More Than the Automatic Query Gives You

When a template needs posts beyond what WordPress automatically queried — a "related posts" section, a custom list by category — WP_Query builds an entirely separate, custom query.

<?php $related = new WP_Query( array( 'post_type' => 'post', 'posts_per_page' => 5, 'category_name' => 'news', ) ); if ( $related->have_posts() ) : while ( $related->have_posts() ) : $related->the_post(); the_title(); endwhile; wp_reset_postdata(); endif; ?>
wp_reset_postdata() is not optional
Looping a custom WP_Query overwrites the same global post data the_post() always sets up — after the custom loop finishes, that global state still points at the last post in the custom query, not back to the original post the main query was on. Any template tags used after the custom loop, but before calling wp_reset_postdata(), will silently reference the wrong post's data — a genuine, common bug with no obvious error message, in the same spirit as WordPress Intermediate/Advanced 1's own wp_head() gotcha.

get_posts() — A Simpler Alternative

get_posts() runs a custom query too, but returns a plain PHP array of post objects directly, rather than a query object requiring have_posts()/the_post().

<?php $recent = get_posts( array( 'numberposts' => 5, 'category_name' => 'news', ) ); foreach ( $recent as $post ) : setup_postdata( $post ); the_title(); endforeach; wp_reset_postdata(); ?>
WP_Queryget_posts()
Returns a full query object with pagination supportReturns a plain array — no built-in pagination
Loop with have_posts()/the_post(), reset with wp_reset_postdata()Loop with an ordinary foreach; still needs setup_postdata()/wp_reset_postdata() if using template tags
The right choice for anything needing pagination or deeper query controlThe right choice for a quick, simple list with no pagination needed

Hands-On Exercises

Exercise 1

A developer adds a "Related Posts" section using WP_Query near the bottom of a single-post template, but forgets to call wp_reset_postdata() afterward. The page footer, which calls the_title() expecting the original post's title, shows the wrong title instead. Explain exactly why.

📄 View solution
Exercise 2

Explain why have_posts() and the_post() "just work" on an ordinary single.php template with no query-writing code anywhere in that file.

📄 View solution
Exercise 3

A developer needs a paginated list of posts filtered by category, with "next page" / "previous page" navigation. Explain whether WP_Query or get_posts() is the better tool here, and why, using this chapter's own comparison.

📄 View solution

Chapter 3 Quick Reference

  • The Loop is an ordinary PHP while loop wrapped around have_posts() (the condition) and the_post() (advances + sets up per-iteration global post data)
  • WordPress automatically builds "the current query" from the requested URL before the template even runs
  • WP_Query — a custom query object with pagination support, looped with have_posts()/the_post(), always paired with wp_reset_postdata() afterward
  • get_posts() — a simpler function returning a plain array, looped with foreach, no built-in pagination
  • Forgetting wp_reset_postdata() leaves global post data pointing at the wrong post — a genuine, silent bug
  • Next chapter: Custom Post Types & Taxonomies