Everything that touches post output hangs off the same filter — autoembeds, wpautop, shortcode expansion, and every plugin. Priority decides the order, and getting it wrong is why a shortcode sometimes renders as text or ends up wrapped in a stray paragraph.
// core, for reference
add_filter( 'the_content', 'wptexturize', 10 );
add_filter( 'the_content', 'wpautop', 10 );
add_filter( 'the_content', 'do_shortcode', 11 );
// after shortcodes have expanded
add_filter( 'the_content', 'my_wrapper', 12 );
do_shortcode runs at 11, after wpautop at 10 — which is exactly why a block-level shortcode on its own line comes out surrounded by an empty <p>. A filter that needs the raw stored content must run below 10; one that needs the finished HTML must run above 11. Returning something other than a string from any of them breaks every filter after it.