query_posts - You should never ever use
query_posts. Apart from what @Rarst has said, the really big issue with
query_posts is, it breaks the main query object ( stored in
$wp_query ). A lot of plugins and custom code relies on the main query object, so breaking the main query object means that you are breaking the functionalities of plugins and custom code. Just one such function is the all important pagination function, so if you break the main query, you break pagination.
To prove how bad
query_posts is, on any template, do the following and compare the results
var_dump( $wp_query );
query_posts( '&posts_per_page=-1' );
var_dump( $wp_query );
WP_Query are the correct way to construct secondary queries ( like related posts, sliders, featured content
and content on static front pages ) with. It should be noted, you should not use any of the two in favor of the main query on the home page, single page or any type of archive page as it will break page functionality. If you need to modify the main query, use
pre_get_posts to do so, and not a custom query. (UPDATE: For static front pages and true pages, see Using pre_get_posts on true pages and static front pages*)
WP_Query is used by the main query and is also used by
get_posts, but although
WP_Query, there are a few differences
get_posts are faster than
WP_Query. The margin depends on the amount of total posts of the site. The reason for this is,
'no_found_rows' => true by default to
WP_Query which skips/legally breaks pagination. With
'no_found_rows' => true,
WP_Query gets the amount of posts queried, then bails out, where by default, it further search for all posts matching the query in order to calculate pagination.
For this reason,
get_posts() should be used for non paginated queries only. Paginating
get_posts is really one big mess.
WP_Query should be used for all paginated queries
get_posts() aren't influenced by the
posts_* filters where
WP_Query gets influenced by these filters. The reason is that
get_posts, by default, passes
'suppress_filters' => true to
get_posts has a couple of extra parameters like
category. These parameters do get changed into valid parameters for
WP_Query before being passed to
include gets changed into
posts_per_page. Just a note, all of the parameters that can be passed to
WP_Query works with
get_posts, you can ignore and not use the default parameters of
get_posts returns just the
$posts property of
WP_Query returns the complete object. This object is quite useful when it comes to conditionals, pagination and other useful info that can be used inside the loop.
get_posts doesn't use the loop, but a
foreach loop to display posts. Also, no template tags are available by default.
setup_postdata( $post ) has to be used to make the template tags available.
WP_Query uses the loop and template tags are available by default
'ignore_sticky_posts' => 1 to
get_posts by default ignores sticky posts
Based on the above, whether to use
WP_Query is up to you and what do you actually need from the query. The above should guide you in your choice