mediumBackend EngineerSaaS
Explain MongoDB aggregation pipeline and how it differs from SQL GROUP BY with practical examples
Posted 18/04/2026
by Mehedy Hasan Ador
Question Details
Interview question:
> "We need a dashboard showing: total applications per month, success rate per company, and average time-to-offer. Can you write both the SQL and MongoDB versions?"
> "We need a dashboard showing: total applications per month, success rate per company, and average time-to-offer. Can you write both the SQL and MongoDB versions?"
Suggested Solution
MongoDB Aggregation Pipeline
db.applications.aggregate([
// Stage 1: Filter
{ $match: { status: { $in: ["OFFER", "REJECTED", "WITHDRAWN"] } } },
// Stage 2: Group by month + company
{
$group: {
id: {
month: { $dateToString: { format: "%Y-%m", date: "$createdAt" } },
company: "$company",
},
total: { $sum: 1 },
offers: {
$sum: { $cond: [{ $eq: ["$status", "OFFER"] }, 1, 0] }
},
avgDaysToOffer: {
$avg: {
$cond: [
{ $eq: ["$status", "OFFER"] },
{ $divide: [
{ $subtract: ["$offerDate", "$dateApplied"] },
86400000 // ms in a day
]},
null
]
}
},
},
},
// Stage 3: Calculate success rate
{
$addFields: {
successRate: { $multiply: [{ $divide: ["$offers", "$total"] }, 100] },
},
},
// Stage 4: Sort
{ $sort: { "id.month": -1, total: -1 } },
// Stage 5: Limit
{ $limit: 100 },
]);
SQL Equivalent
SELECT
TOCHAR(dateapplied, 'YYYY-MM') AS month,
company,
COUNT(*) AS total,
SUM(CASE WHEN status = 'OFFER' THEN 1 ELSE 0 END) AS offers,
ROUND(
100.0 * SUM(CASE WHEN status = 'OFFER' THEN 1 ELSE 0 END) / COUNT(*),
2
) AS successrate,
AVG(
CASE WHEN status = 'OFFER'
THEN EXTRACT(EPOCH FROM (offerdate - dateapplied)) / 86400
END
) AS avgdaystooffer
FROM applications
WHERE status IN ('OFFER', 'REJECTED', 'WITHDRAWN')
GROUP BY TOCHAR(dateapplied, 'YYYY-MM'), company
ORDER BY month DESC, total DESC
LIMIT 100;
Pipeline Stages Cheat Sheet
$match$group$sort$limit$skip$project$lookup$unwind$facet$addFieldsPerformance Tips
1. Put$match first — Reduces documents flowing through pipeline2. Use indexes —
$match can use indexes if first stage3. Avoid
$unwind on large arrays — Creates a document per element4. Use
$facet for multiple aggregations — Runs in parallel on same inputdb.applications.aggregate([
{ $facet: {
byStatus: [{ $group: { id: "$status", count: { $sum: 1 } } }],
byMonth: [{ $group: { id: { $month: "$createdAt" }, count: { $sum: 1 } } }],
totalApplications: [{ $count: {} }],
}}
]);