Best practice custom function, where to echo the variables?

Generically speaking, yes. If your normal output would be to echo at the end, echoing during the loop wouldn’t change much.

function get_some_posts($args) { 
        $query = new WP_Query( $args );

        if ( $query->have_posts() ) {

            while ( $query->have_posts() ) {

                $query->the_post();
                the_title(); // the_title() is the same as echo get_the_title();
            }

        }
}
add_action('custom_hook','get_some_posts');

The output of the above should be the same as the output below:

function get_some_posts($args) { 
        $query = new WP_Query( $args );

        if ( $query->have_posts() ) {

            // declare your variable to avoid php output errors
            $to_echo = '';

            while ( $query->have_posts() ) {

                $query->the_post();

                $to_echo .= get_the_title(); // ".=" adds to the current value of the variable.
            }

        }
        //And then echo it at the end of the function?
        echo $to_echo;
}
add_action('custom_hook','get_some_posts');

Of course, I’m working straight off of your question’s code. This lacks a little context and you would also want to apply some sort of space or line break between the titles.

By way of practical example, and your question about not echoing it in The Loop, here’s a possibility just as an example:

add_filter( 'the_content', 'get_some_posts' );
function get_some_posts( $content ) { 

    if ( is_single() ) {

        $query = new WP_Query( array( 'post_type' => 'page' ) );

        if ( $query->have_posts() ) {

            // declare your variable to avoid php output errors
            $to_echo = '';

            while ( $query->have_posts() ) {

                $query->the_post();

                $to_echo .= get_the_title() . "<br />";
            }

        }

        $content = $content . "<p>Checkout some pages:</p>" . $to_echo;
    }

    return $content;
}

This would take your “theoretical” and apply it to WP’s “the_content” filter which filters the main content area. It first checks if we’re on a single post, and if so, runs a query to get pages. It puts the titles of those pages into a list, adds some text to it, then tacks in onto the $content variable (coming through the filter) before sending back the result.

It’s not a “real world” example in terms of final use because I didn’t actually include links – just the titles, and it’s not limited in number so it could be a ridiculous amount of extra content. It’s merely here as an example for your question of “how” can you take a result from WP_Query and return it. So it’s an example of what “filters” do (or might do). If it were an action, then that would be different. You’d either have to echo it in the function OR put it into a variable declared as a global and pick that global up in a later action/filter.