Grouping Logs Together with console.group() JS
Learn how to organize JavaScript console output using console.group(), console.groupCollapsed(), and console.groupEnd(). Build structured, readable debugging sessions with nested groups and real-world patterns.
When you debug a complex process, a flat stream of console.log() output quickly becomes unreadable. An authentication flow might produce 15 log messages, an API response handler another 10, and suddenly you are scrolling through dozens of unlabeled lines trying to find the one value that matters.
console.group() solves this by letting you wrap related console messages in collapsible, labeled sections. You can nest groups inside groups, collapse verbose output by default, and use console.warn() or console.error() inside groups to highlight the important lines. The result is console output that reads like an outline instead of a wall of text.
How console.group() Works
The grouping API consists of three methods:
| Method | Purpose |
|---|---|
console.group(label) | Starts a new group (expanded by default) |
console.groupCollapsed(label) | Starts a new group (collapsed by default) |
console.groupEnd() | Closes the most recently opened group |
Everything logged between console.group() and console.groupEnd() appears indented under the group label. In Chrome DevTools, Firefox Console, and Edge DevTools, the group label is clickable and toggles the visibility of its contents.
Basic Group Example
console.group('User Profile Load');
console.log('Fetching user ID: 12345');
console.log('Response status: 200');
console.log('User name: Alice Chen');
console.log('Role: admin');
console.groupEnd();
console.group('Dashboard Data');
console.log('Loading charts...');
console.log('5 widgets initialized');
console.groupEnd();Output in the browser console:
▼ User Profile Load
Fetching user ID: 12345
Response status: 200
User name: Alice Chen
Role: admin
▼ Dashboard Data
Loading charts...
5 widgets initialized
Each group is collapsible. Clicking "User Profile Load" hides those four lines, letting you focus on "Dashboard Data" or vice versa.
console.groupCollapsed() for Verbose Output
When debugging output is useful for reference but does not need immediate attention, console.groupCollapsed() starts the group in a collapsed state:
async function loadProductCatalog() {
console.group('Product Catalog');
console.log('Fetching catalog from API...');
const response = await fetch('/api/products');
const products = await response.json();
console.log(`Loaded ${products.length} products`);
// Detailed per-product data — collapsed by default
console.groupCollapsed(`Product Details (${products.length} items)`);
products.forEach(product => {
console.log(`${product.name}: $${product.price} (${product.stock} in stock)`);
});
console.groupEnd(); // Closes "Product Details"
console.log('Catalog ready');
console.groupEnd(); // Closes "Product Catalog"
return products;
}Output:
▼ Product Catalog
Fetching catalog from API...
Loaded 48 products
▶ Product Details (48 items) ← collapsed, click to expand
Catalog ready
The summary ("Loaded 48 products") is visible immediately. The 48 individual product lines are hidden behind a click. This keeps the console clean while preserving all the detail when you need it.
Nesting Groups
Groups can be nested to any depth. This maps naturally to processes that have sub-steps:
function processCheckout(cart) {
console.group('Checkout Process');
console.group('Cart Validation');
console.log(`Items: ${cart.items.length}`);
console.log(`Subtotal: $${cart.subtotal}`);
const invalidItems = cart.items.filter(item => item.stock === 0);
if (invalidItems.length > 0) {
console.warn(`${invalidItems.length} items are out of stock`);
} else {
console.log('All items in stock');
}
console.groupEnd(); // Closes "Cart Validation"
console.group('Payment Processing');
console.log(`Payment method: ${cart.paymentMethod}`);
console.log('Contacting payment gateway...');
console.log('Payment authorized');
console.groupEnd(); // Closes "Payment Processing"
console.group('Order Confirmation');
console.log(`Order ID: ORD-${Date.now()}`);
console.log('Confirmation email sent');
console.groupEnd(); // Closes "Order Confirmation"
console.groupEnd(); // Closes "Checkout Process"
}Output:
▼ Checkout Process
▼ Cart Validation
Items: 3
Subtotal: $127.50
All items in stock
▼ Payment Processing
Payment method: credit_card
Contacting payment gateway...
Payment authorized
▼ Order Confirmation
Order ID: ORD-1709546400000
Confirmation email sent
Each sub-step is independently collapsible. You can collapse "Cart Validation" and "Order Confirmation" to focus on "Payment Processing" when debugging a payment issue.
Real-World Patterns
Pattern 1: Grouping API Request and Response
Wrap the full lifecycle of an API call in a group so the request details, timing, and response are always together:
async function apiCall(endpoint, options = {}) {
const method = options.method || 'GET';
const label = `${method} ${endpoint}`;
console.group(label);
console.time(label);
if (options.body) {
console.groupCollapsed('Request Body');
console.log(JSON.parse(options.body));
console.groupEnd();
}
try {
const response = await fetch(endpoint, options);
const data = await response.json();
console.log('Status:', response.status);
console.groupCollapsed('Response Body');
console.log(data);
console.groupEnd();
console.timeEnd(label);
console.groupEnd();
return data;
} catch (error) {
console.error('Request failed:', error.message);
console.timeEnd(label);
console.groupEnd();
throw error;
}
}
// Usage
await apiCall('/api/users', {
method: 'POST',
body: JSON.stringify({ name: 'Alice', role: 'admin' }),
headers: { 'Content-Type': 'application/json' },
});Output:
▼ POST /api/users
▶ Request Body ← collapsed
Status: 201
▶ Response Body ← collapsed
POST /api/users: 142ms
Pattern 2: Grouping Loop Iterations
When debugging a loop, group each iteration so you can collapse the ones that work correctly and focus on the problematic one:
function processInvoices(invoices) {
console.group(`Processing ${invoices.length} invoices`);
invoices.forEach((invoice, index) => {
const hasIssue = invoice.total <= 0 || !invoice.customer;
const groupMethod = hasIssue ? 'group' : 'groupCollapsed';
console[groupMethod](`Invoice #${invoice.id} (${invoice.customer || 'MISSING'})`);
console.log('Total:', invoice.total);
console.log('Items:', invoice.items.length);
console.log('Date:', invoice.date);
if (invoice.total <= 0) {
console.error('Invalid total: must be positive');
}
if (!invoice.customer) {
console.error('Missing customer name');
}
console.groupEnd();
});
console.groupEnd();
}This approach automatically expands invoices with problems and collapses healthy ones. You see the errors without scrolling past hundreds of "everything is fine" lines.
Pattern 3: Event Handler Debugging
When multiple events fire in sequence, group them to see the flow:
function attachFormDebugger(form) {
form.addEventListener('focus', (e) => {
console.groupCollapsed(`Focus: ${e.target.name || e.target.tagName}`);
console.log('Element:', e.target);
console.log('Related target:', e.relatedTarget);
console.groupEnd();
}, true);
form.addEventListener('input', (e) => {
console.groupCollapsed(`Input: ${e.target.name}`);
console.log('Value:', e.target.value);
console.log('Valid:', e.target.validity.valid);
console.groupEnd();
});
form.addEventListener('submit', (e) => {
e.preventDefault();
console.group('Form Submit');
const formData = new FormData(form);
for (const [key, value] of formData) {
console.log(`${key}: ${value}`);
}
console.groupEnd();
});
}Pattern 4: Conditional Debug Groups
Create a utility function that only produces grouped output in development:
const debugGroup = {
start(label) {
if (process.env.NODE_ENV === 'development') {
console.group(label);
}
},
startCollapsed(label) {
if (process.env.NODE_ENV === 'development') {
console.groupCollapsed(label);
}
},
end() {
if (process.env.NODE_ENV === 'development') {
console.groupEnd();
}
},
};
// Usage — groups only appear in development
debugGroup.start('Auth Flow');
console.log('Checking token...');
debugGroup.end();Combining Groups with Other Console Methods
Groups work with every other console method. Combine them for rich, organized output:
function auditUserPermissions(user) {
console.group(`Permission Audit: ${user.name}`);
console.table(user.permissions.map(p => ({
resource: p.resource,
action: p.action,
granted: p.granted ? 'Yes' : 'No',
})));
const deniedCount = user.permissions.filter(p => !p.granted).length;
if (deniedCount > 0) {
console.warn(`${deniedCount} permissions are denied`);
}
console.assert(
user.permissions.some(p => p.resource === 'admin'),
'User has no admin-related permissions'
);
console.time('Policy evaluation');
// ... evaluate complex access policies
console.timeEnd('Policy evaluation');
console.groupEnd();
}Best Practices
Using Groups Effectively
These practices prevent console groups from becoming more cluttered than the flat output they replaced.
Always close your groups. Every console.group() or console.groupCollapsed() needs a matching console.groupEnd(). An unclosed group causes all subsequent console output to be indented inside it, which is confusing and can persist across unrelated function calls.
Use groupCollapsed for verbose data. If a group contains more than 5 lines or logs iteration details, start it collapsed. The summary should be visible at the group label level; the details should be one click away.
Use descriptive group labels. console.group('Process') tells you nothing. console.group('POST /api/orders - Processing 5 items') tells you exactly what happened. Include the entity ID, count, or key identifier in the label.
Limit nesting to 2-3 levels. Deeply nested groups are harder to read than flat output. If you need more than 3 levels, consider splitting the logic into separate functions with their own top-level groups.
Remove groups in production. Like all console output, grouped logs should be stripped from production builds. Groups are a development tool, not a logging framework.
Common Mistakes and How to Avoid Them
Group Pitfalls to Watch For
These mistakes make grouped output harder to read instead of easier.
Missing console.groupEnd() in error paths. If your function has early returns or try-catch blocks, the group might never close:
// Bad — group stays open if fetch fails
function loadData() {
console.group('Loading data');
try {
const data = fetchSync('/api/data');
console.log('Loaded:', data);
} catch (error) {
console.error('Failed:', error);
return; // Group is never closed!
}
console.groupEnd();
}
// Good — groupEnd in finally
function loadData() {
console.group('Loading data');
try {
const data = fetchSync('/api/data');
console.log('Loaded:', data);
} catch (error) {
console.error('Failed:', error);
} finally {
console.groupEnd(); // Always closes
}
}Grouping single log statements. A group with one console.log() inside it adds clutter without adding clarity. Only group when you have 3+ related messages.
Using expanded groups for high-frequency events. If you log every mousemove or scroll event in expanded groups, the console becomes unusable. Use groupCollapsed for events that fire rapidly, or add a counter and only log every Nth event.
Forgetting that groups are visual-only. console.group() has no effect on program execution or data flow. It purely organizes the console display. Do not use it as a replacement for structured logging in production.
Browser Compatibility
| Method | Chrome | Firefox | Safari | Edge | Node.js |
|---|---|---|---|---|---|
console.group() | Yes | Yes | Yes | Yes | Yes (no collapse) |
console.groupCollapsed() | Yes | Yes | Yes | Yes | Same as group() |
console.groupEnd() | Yes | Yes | Yes | Yes | Yes |
Node.js Behavior
In Node.js, console.group() adds indentation to output but does not create collapsible sections (there is no interactive console). console.groupCollapsed() behaves identically to console.group() in Node.js.
Next Steps
Practice grouping in a real debugging session
Take a feature you are currently working on and add console.group() around its main operations. Observe how quickly you can isolate problems compared to flat log output.
Learn browser debugging beyond the console
Move beyond console methods by learning basic JavaScript debugging tips, including breakpoints and the DevTools Sources panel.
Master stack traces for error diagnosis
When console groups reveal an error, learn to read JavaScript stack traces to trace it back to the root cause.
Build a reusable debug logger
Create a utility module that wraps console.group() with environment checks, automatic timing, and structured labels. This becomes your team's standard debugging interface.
Rune AI
Key Insights
- Three methods:
console.group()opens expanded,console.groupCollapsed()opens collapsed,console.groupEnd()closes the current group - Descriptive labels are essential: include the operation name, entity ID, or item count in the group label for instant context
- Use
groupCollapsedfor verbose output: keep summaries visible and hide details behind one click - Always close groups: use
finallyblocks to prevent unclosed groups from indenting all subsequent output - Development only: strip all console group calls from production builds just like any other console method
Frequently Asked Questions
Does console.group() affect performance?
Can I use console.group() in Node.js?
How deep can I nest console groups?
What happens if I forget console.groupEnd()?
Can I style console group labels?
Conclusion
console.group() transforms flat, scrollable debugging output into organized, collapsible sections that match the logical structure of your code. By grouping API calls, loop iterations, event handlers, and multi-step workflows, you can collapse the noise and focus on the specific area where a bug lives. Combined with console.groupCollapsed() for verbose data and nested groups for sub-steps, this method turns the browser console into a structured debugging interface.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.