Every performance audit I run turns up the same three misuses of WP_Query. Here they are, in order of how often they cost real money.
1. no_found_rows
If you’re not paginating, set this to true. It skips the SQL_CALC_FOUND_ROWS that WordPress adds by default, which is often the most expensive part of the query on large tables.
2. update_post_meta_cache and update_post_term_cache
If your loop only prints titles and permalinks, disable both. You’ll save two queries per request and a lot of memory on archive pages.
3. fields => 'ids'
When you only need IDs — for a related-posts widget, a sitemap, a cache key — asking for full WP_Post objects is wasteful. Ask for IDs and hydrate lazily.