For more tips like this, sign up to the weekly newsletter!

Using console.group for hierarchical logging

Recursive functions are usually very hard to debug, especially with logging. They tend to produce a plethora of entries.

Consider a simple recursive function, for example, a merge sort:

function mergeSort(arr) {    
  if (arr.length < 2) {
    return arr;
  }

  const middle = Math.floor(arr.length / 2);
  const left = mergeSort(arr.slice(0, middle));
  const right = mergeSort(arr.slice(middle));

  const result = merge(left, right);
  return result;
}

function merge(a, b) {
  let i = 0, j = 0, result = [];
  while (i < a.length || j < b.length) {
    if (j >= b.length || a[i] < b[j]) {
      result.push(a[i++]);
    }else {
      result.push(b[j++]);
    }
  }
  return result;
}

console.log(mergeSort([2, 1, 3]));	// [1, 2, 3]

Add some logging

Then add some logging to see what is going on inside the functions:

function mergeSort(arr) {    
  console.log(`Sorting ${arr}`);
  if (arr.length < 2) {
    console.log("Already sorted");
    return arr;
  }

  const middle = Math.floor(arr.length / 2);
  console.log(`Middle: ${middle}`);
  const left = mergeSort(arr.slice(0, middle));
  const right = mergeSort(arr.slice(middle));

  const result = merge(left, right);
  console.log(`Result: ${result}`);
  return result;
}

function merge(a, b) {
  console.log(`Merging ${a} with ${b}`);
  let i = 0, j = 0, result = [];
  while (i < a.length || j < b.length) {
    if (j >= b.length || a[i] < b[j]) {
      console.log(`Pushing ${a[i]}`);
      result.push(a[i++]);
    }else {
      console.log(`Pushing ${b[j]}`);
      result.push(b[j++]);
    }
  }
  return result;
}

console.log(mergeSort([2, 1, 3]));	// [1, 2, 3]

This logging generates the rather useless output of intertwined log entries:

Sorting 2,1,3
Middle: 1
Sorting 2
Already sorted
Sorting 1,3
Middle: 1
Sorting 1
Already sorted
Sorting 3
Already sorted
Merging 1 with 3
Pushing 1
Pushing 3
Result: 1,3
Merging 2 with 1,3
Pushing 1
Pushing 2
Pushing 3
Result: 1,2,3

The above output is hardly helpful in any real-world situation.

Meet console.group

For a better solution, use console.group(name) and console.groupEnd()! They add hierarchy to the log messages, so you instantly see which level emitted the message.

console.group(name) works like console.log(message), but it opens a new group as well as outputs the message.

console.groupEnd() closes the deepest group. group and groupEnd should always go in pairs.

If you see too many console messages, use console.groupCollapsed(name) instead, which shows the group collapsed by default.

With just a few lines of code, you can add grouping.

function mergeSort(arr) {    
  console.group(`Sorting ${arr}`);
  if (arr.length < 2) {
    console.log("Already sorted");
    console.groupEnd();
    return arr;
  }

  const middle = Math.floor(arr.length / 2);
  console.log(`Middle: ${middle}`);
  const left = mergeSort(arr.slice(0, middle));
  const right = mergeSort(arr.slice(middle));

  const result = merge(left, right);
  console.log(`Result: ${result}`);
  console.groupEnd();
  return result;
}

function merge(a, b) {
  console.group(`Merging ${a} with ${b}`);
  let i = 0, j = 0, result = [];
  while (i < a.length || j < b.length) {
    if (j >= b.length || a[i] < b[j]) {
      console.log(`Pushing ${a[i]}`);
      result.push(a[i++]);
    }else {
      console.log(`Pushing ${b[j]}`);
      result.push(b[j++]);
    }
  }
  console.groupEnd();
  return result;
}

console.log(mergeSort([2, 1, 3]));	// [1, 2, 3]

Running this code gives a useful log:

Sorting 2,1,3
  Middle: 1
  Sorting 2
    Already sorted
  Sorting 1,3
    Middle: 1
    Sorting 1
      Already sorted
    Sorting 3
      Already sorted
    Merging 1 with 3
      Pushing 1
      Pushing 3
    Result: 1,3
  Merging 2 with 1,3
    Pushing 1
    Pushing 2
    Pushing 3
  Result: 1,2,3

Browser support

Although not being a web standard, all the major browsers support grouping (IE >= 11).

Try it
References
Learn more: