Why does get_post() return the latest post inside template_include before doing anything

I tried this in a fresh install of WP locally so I’d assume this is expected behavior, but I don’t understand it. I’m not in the loop,

No but that doesn’t mean a main query doesn’t exist, it just means your code to display it hasn’t run yet

and I haven’t called anything to start a query,

But the main query has started and completed, it needs to in order for conditionals used to decide which template to load to work.

why would get_post() return the first post? Same result if I use global $post.

Why wouldn’t it? Note that you get the same result because they are literally the same thing.

When you load a URL several things happen:

  • the pretty URL gets matched against a rule that turns it into an ugly URL aka index.php?foo=bar
  • those URL parameters get whitelisted and fed into the main query WP_Query, which is what the main loop functions operate on.
  • that query is used to power functions like is_single or is_archive etc
  • WP uses those functions to make a decision about which theme template or block template to load.

So the main query has already fetched the posts, and the first post is already available. If it wasn’t, there’d be no way to catch 404s or load page templates for pages.

Ofcourse, you’ve not called the_post() yet which might be why there’s some confusion, but that makes more sense if you know these things:

  • if you don’t tell get_post which post to get, it defaults to the current post, aka global $post. It’s the very first thing it does in the function
    • if you call this too early it can return null
  • In the main WP class there’s a function named register_globals that sets up initial values for major globals, including $post, which at the time of writing has a line that looks like this: $GLOBALS['post'] = isset( $wp_query->post ) ? $wp_query->post : null;
    • it too will set it to null if no posts were found to avoid problems

Remember, all frontend pages have a main query, even if you use get_posts or new WP_Query in your templates.