Display category title and three latest post inside the category

posts_per_page=-1 is perfectly fine. This is used to get all posts. As for your code, you should never ever use query_posts unless you want to intensionally break stuff on your pages. This is one of the most common functions used and yet the one function that causes the most problems in a website.

I have recently done a post on custom queries and have included many important links to important posts that you have to read, including why you should never use query_posts. You have stated in your previous question that you need to learn a lot, well, this post and the linked ones should be a great learning curve :-). Please check it out here.

What I find extremely odd about your code is that you are using a counter for category ID’s. This is really unpredictable, because all tags, categories and custom taxonomy terms and post formats share the same tables, so the ID’s are not sequential for a given taxonomy. You should rather make use of get_categories() to gt an array of all the categories. More reliable, and you have the exclude parameter to exclude categories and the include parameter if you only need specific categories

You will need to run a separate query for every category. This can be quite resource intensive, so you might want to look at putting the results in a transient (check this post) or a cache.

You can try something in the line of this:

$categories = get_categories(array('exclude' => '4, 7'));

foreach ($categories as $category) {

    $args = array(
        'cat' => $category->cat_ID,
        'posts_per_page' => 3
    );
    $q = new WP_Query($args);
    while($q->have_posts()) {
        $q->the_post();

        //your template tags and HTML mark up

    }
    wp_reset_postdata();

}

EDIT

Just a last note, something that I have initially missed, you only do one while() loop, not two as in your question 🙂