What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Duplicate posts usually have one of two causes: the same query is being rendered twice, or separate queries are returning overlapping posts. Rewind a query only when you intentionally want a second pass through the same results. For separate sections, use separate WP_Query objects, restore post data after each custom loop, and pass displayed IDs to later queries with post__not_in when each post must appear only once.
First identify which kind of duplication you have
Inspect the arguments and purpose of every loop before changing code. A page can show the same posts twice deliberately—for example, once as a compact contents list and again as full cards—or it can have two different sections whose filters overlap.
- Same query, two presentations: use the same query object again, but rewind it before the second pass.
- Different sections: create separate queries and decide whether overlap is allowed.
- Unexpected template output after a custom loop: restore the main query’s post context with
wp_reset_postdata(). - Main archive parameters need changing: modify the main query with a carefully scoped
pre_get_postscallback.
Use rewind_posts() only for a second pass through the same query
A query object keeps an internal position as the_post() advances through its results. Once the first loop reaches the end, call rewind_posts() before iterating over that exact object again.
$posts_query = new WP_Query( $args );
if ( $posts_query->have_posts() ) {
while ( $posts_query->have_posts() ) {
$posts_query->the_post();
// First presentation.
}
$posts_query->rewind_posts();
while ( $posts_query->have_posts() ) {
$posts_query->the_post();
// Second presentation of the same results.
}
}
wp_reset_postdata();
Rewinding resets the post index; it does not remove duplicates between two different queries. If the second section has its own arguments, use a new query and apply an exclusion strategy instead.
#1 Best Overall
Build independent sections with secondary WP_Query objects
A secondary query has its own result set, but calling the_post() still changes the global post context used by template tags such as the_title() and the_permalink(). The conventional pattern is to reset that context immediately after each custom loop.
$custom_query = new WP_Query( $args );
if ( $custom_query->have_posts() ) {
while ( $custom_query->have_posts() ) {
$custom_query->the_post();
// Render this post.
}
}
wp_reset_postdata();
wp_reset_postdata() restores the global $post to the current post in the main query. Without it, markup that follows the custom loop can display fields from the last secondary-query post, which may look like another duplication or misplaced content.
Rank #2
Make multiple sections mutually exclusive with post__not_in
When a post may match both sections but should be rendered only once on the page, collect the IDs actually displayed by the first query. Add those IDs to the second query’s post__not_in argument.
$shown_ids = array();
$first_query = new WP_Query( $first_args );
while ( $first_query->have_posts() ) {
$first_query->the_post();
$shown_ids[] = get_the_ID();
// Render the first section.
}
wp_reset_postdata();
$second_args['post__not_in'] = $shown_ids;
$second_query = new WP_Query( $second_args );
while ( $second_query->have_posts() ) {
$second_query->the_post();
// Render only posts not shown above.
}
wp_reset_postdata();
The exclusion list must reflect the posts your implementation really rendered—not merely IDs returned by a query if some results were skipped by permissions, conditions, or display limits. If there are three or more sections, update the same list after each section before constructing the next query.
Recommended Free Tools
Rank #3
Pagination and scope considerations
Decide what “already shown” means for the page. If the first section is paginated, the list may contain only that page’s rendered IDs; if the requirement is global uniqueness across all pages or sections, design the pagination and accumulation rules accordingly. Theme builders, plugins, block-based Query Loop blocks, and custom hooks can create additional queries that need to be inspected separately.
Choose the right mechanism for the job
| Need | Use | Important caution |
|---|---|---|
| Present one result set twice | rewind_posts() before the second pass |
The same posts are intentionally shown again. |
| Render another group of posts | A new WP_Query (or get_posts() for a simple array) |
Reset post data after a custom WP_Query loop and check for overlap. |
| Exclude posts rendered earlier | Accumulate IDs and pass post__not_in |
Ensure the IDs cover the intended exclusion scope. |
| Change archive or home query criteria | pre_get_posts with guards |
Do not alter admin or unrelated custom queries. |
| Replace the global main query | Do not use query_posts() |
It overrides and reruns the main query. |
Do not use query_posts() to create another loop
query_posts() replaces the main query rather than providing a clean secondary query. It can discard results WordPress already fetched and execute another database query, while also disturbing pagination and global state. It must not be used inside the WordPress Loop for this purpose.
Use new WP_Query( $args ) for a separate section. For a straightforward retrieval where you only need an array of posts, get_posts( $args ) can be appropriate; for multiple loops and explicit query control, the direct WP_Query form is usually clearer.
Modify the main query with a guarded pre_get_posts callback
If the archive or home page itself needs different criteria, change that query before it runs instead of replacing it. Scope the callback to the intended request and query object so that sidebars, footers, and other custom loops remain untouched.
Best Value
function site_adjust_main_query( $query ) {
if ( is_admin() || ! $query->is_main_query() ) {
return;
}
if ( $query->is_home() ) {
$query->set( 'posts_per_page', 10 );
// Set only the criteria required for this main query.
}
}
add_action( 'pre_get_posts', 'site_adjust_main_query' );
The hook runs before execution. An unscoped callback can unintentionally change custom queries and make duplicate or missing posts appear in otherwise unrelated components.
Quick Recap
A practical troubleshooting sequence
- List every query: record its arguments, section, pagination, and whether it is the main query or a custom one.
- Compare result intent: determine whether two loops intentionally share a result set or should be disjoint.
- Check query position: if the same object is reused, rewind it before the second iteration.
- Check overlap: for distinct sections, collect IDs as they are rendered and apply
post__not_into later queries. - Restore context: call
wp_reset_postdata()after every secondaryWP_Queryloop. - Search for query replacement: remove
query_posts()and replace it with a secondary query or a guardedpre_get_postschange. - Inspect generated queries: if the standard pattern is correct, examine builder-generated loops, plugins, hooks, and block Query Loop settings for an additional source of output.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

