Does WP_Query ‘responds’ badly to empty arguments?

I would not write it the same way, because the more cluttered a query gets, the more likely it is to return unexpected results.

Here’s how I’d handle your code:

$args = array(); // All your WP_Query args go here.

// You don't need to pass 'post_status' => 'publish' because that's default

// Replace all instances of [ isset && '' !== ] with !empty
if ( !empty( $_GET['cat'] ) ) 
    $args['cat'] = sanitize_text_field( wp_unslash( $_GET['cat'] ) );

if ( !empty( $_GET['tag'] ) )
    $args['tag_id'] = sanitize_text_field( wp_unslash( $_GET['tag'] ) );

if ( !empty( $_GET['year'] ) || !empty( $_GET['month'] ) ) {
    $date_query = array();
    if ( !empty( $_GET['year'] ) )
        $date_query['year'] = sanitize_text_field( wp_unslash( $_GET['year'] ) );
    if ( !empty( $_GET['month'] ) )
        $date_query['month'] = sanitize_text_field( wp_unslash( $_GET['month'] ) );
    $args['date_query'] = array( $date_query );
}

if ( !empty( $_GET['post__not_in'] ) )
     $args['post__not_in'] = sanitize_text_field( wp_unslash( $_GET['post__not_in'] ) );

$the_query = new WP_Query( $args );

It seems a little weird to me that you’d need to unslash paired with sanitize, but if you feel it’s necessary, I’ll leave it in for the time being, otherwise, I’d probably only run sanitize.

As it pertains to AJAX, I haven’t personally used no_found_rows, so I can’t speak to that. The AJAX protocols I’ve written usually are paired with an input that has the current offset of “pages” of posts being shown.