MongoDB
aggregation framework
$group stage
$push operator
database fields

Mongo group and push pushing all fields

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

$group and $push are a common MongoDB pattern when you need grouped summaries plus raw records for each group. The tricky part is deciding what to push, because pushing full documents can inflate memory use and payload size quickly. A better approach is to group deliberately, include only required fields, and add sorting or filtering before grouping.

What $group and $push Actually Do

$group collects documents by a key and computes accumulator outputs per group. $push appends one value per input document into an array in that grouped output.

A minimal pattern looks like this:

javascript
1db.orders.aggregate([
2  {
3    $group: {
4      _id: "$customerId",
5      orders: { $push: "$orderId" }
6    }
7  }
8])

Here each result row is one customer with an orders array.

When people say "push all fields", they often mean one of two goals:

  • Keep each original document in grouped output.
  • Keep a curated subset of fields that represent each document.

The second option is usually safer for performance and maintenance.

Push Full Document vs Curated Object

If you truly need the complete source row, use $$ROOT.

javascript
1db.orders.aggregate([
2  {
3    $group: {
4      _id: "$customerId",
5      docs: { $push: "$$ROOT" },
6      totalAmount: { $sum: "$amount" }
7    }
8  }
9])

This is convenient, but docs can become very large. In many APIs, a curated object is better:

javascript
1db.orders.aggregate([
2  {
3    $group: {
4      _id: "$customerId",
5      docs: {
6        $push: {
7          orderId: "$orderId",
8          amount: "$amount",
9          status: "$status",
10          createdAt: "$createdAt"
11        }
12      },
13      totalAmount: { $sum: "$amount" },
14      count: { $sum: 1 }
15    }
16  }
17])

This keeps output stable even when source documents gain new fields later.

Sort and Filter Before Grouping

$push preserves input order, so stage order matters. If you want newest orders first inside each grouped array, sort before grouping.

javascript
1db.orders.aggregate([
2  { $match: { createdAt: { $gte: ISODate("2026-01-01T00:00:00Z") } } },
3  { $sort: { customerId: 1, createdAt: -1 } },
4  {
5    $group: {
6      _id: "$customerId",
7      docs: {
8        $push: {
9          orderId: "$orderId",
10          amount: "$amount",
11          createdAt: "$createdAt"
12        }
13      }
14    }
15  }
16])

Without pre-sort, array order depends on upstream execution and is not safe to treat as business logic.

Add Post-Group Transformations

You can compute summary fields and shape final output with $project.

javascript
1db.orders.aggregate([
2  {
3    $group: {
4      _id: "$customerId",
5      docs: {
6        $push: {
7          orderId: "$orderId",
8          amount: "$amount",
9          status: "$status"
10        }
11      },
12      totalAmount: { $sum: "$amount" },
13      count: { $sum: 1 }
14    }
15  },
16  {
17    $project: {
18      _id: 0,
19      customerId: "$_id",
20      count: 1,
21      totalAmount: 1,
22      averageAmount: {
23        $cond: [
24          { $eq: ["$count", 0] },
25          0,
26          { $divide: ["$totalAmount", "$count"] }
27        ]
28      },
29      docs: 1
30    }
31  }
32])

This gives an API-ready shape and avoids leaking internal _id grouping keys when not needed.

Performance Guidance for Large Groups

Grouping into arrays can hit memory limits or produce oversized documents. Keep these guardrails:

  • Use $match early to reduce input rows.
  • Push only fields consumers need.
  • Consider paginated drill-down queries instead of embedding huge arrays.
  • Add indexes that support your $match and $sort stages.
  • Use allowDiskUse: true for heavy jobs when appropriate.

If a group can grow unbounded, design for summary output in aggregation and fetch detailed rows in a second query.

Common Pitfalls

  • Using $$ROOT everywhere and creating massive grouped documents.
  • Expecting deterministic order in pushed arrays without an explicit pre-group sort.
  • Grouping first and filtering later, which wastes resources.
  • Forgetting MongoDB document size constraints when arrays grow.
  • Returning internal fields directly to clients without a projection step.

Summary

  • $group plus $push is powerful for grouped detail and summary in one pipeline.
  • Use $$ROOT only when full documents are truly required.
  • Prefer curated pushed objects for stable contracts and better performance.
  • Sort and filter before grouping to control array order and reduce workload.
  • Plan for large-group behavior with indexing, projection, and bounded payload design.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.