the_content filter order decides who wins

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.