How does the $post_type global variable work?

The global $post_type is usually from the public query variable of the main query $wp_query, but get_post_type() is the post type of the current $post object, within the loop.

So for e.g. secondary loops this can be different.

The global $post_type might not always be set, as the default post type for WP_Query is the post post type.

From WP::register_globals() we can see how the post_type query variable becomes globally accessible:

// Extract updated query vars back into global namespace.
foreach ( (array) $wp_query->query_vars as $key => $value ) {
    $GLOBALS[ $key ] = $value;
}

as I posted in my recent answer here.

In general I would use the WordPress API calls, instead of using direct globals, as the former usually is designed to handle various cases, including defaults, and is updated when needed.

Working directly with the globals increases the risk of overriding them.

If your goal is to setup some CSS selectors, one can often just use the ones provided for the <body> tag, with the body_class() core function. Also consider the post_class() core function, within your loop, as it already contains the post type class info.