[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-object-groupby-replace-reduce":32,"quiz-article-object-groupby-replace-reduce":48},[4,20],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Build. Learn. Ship.","Practical courses, engineering-grade articles and open-source tools for people who ship.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Structured paths, listening drills and spaced repetition that actually sticks.","amber-400","⌘",[13,15,29],"es",{"courses":17,"paths":17,"articles":18,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":17},2,{"id":33,"slug":34,"kind":35,"title":36,"description":37,"config":38,"verticalId":5,"vertical":43,"course":40,"_count":44,"access":45,"attempts":47,"questionCount":39},"019fe776-48e4-728a-bfdb-49298f576f76","object-groupby-replace-reduce","PRACTICE_QUIZ","Object.groupBy and Map.groupBy","Grouping an array by a key without the hand-written reduce. What Object.groupBy returns, when to reach for Map.groupBy instead, and the null-prototype detail that trips people up.",{"questionCount":39,"timeLimitSec":40,"shuffleQuestions":18,"shuffleOptions":17,"negativeMarking":19,"passScorePct":41,"maxAttempts":40,"revealAnswers":42,"allowFlagging":18,"allowBacktracking":17},6,null,70,"IMMEDIATE",{"slug":6,"name":7},{"questions":39},{"allowed":17,"reason":46},"FREE",[],{"id":49,"slug":34,"title":50,"subtitle":40,"excerpt":51,"coverUrl":52,"locale":13,"readingMinutes":53,"publishedAt":54,"viewCount":55,"likeCount":19,"commentCount":19,"author":56,"vertical":61,"topic":62,"tags":65,"_count":77,"playground":79,"body":81,"bodyMd":231,"seo":232,"translationGroupId":236,"thread":237,"assessments":239,"translations":242,"quiz":244},"019fe660-2b2e-71fb-a0d1-fd1509765d3b","You've been writing this `reduce` a hundred times. `Object.groupBy` does it in one.","Grouping an array by a key is one of the most common data transforms in frontend code. You've been wiring it by hand with `reduce` for years. `Object.groupBy` is the native version — no helper, no library, no ceremony.","\u002Fmedia\u002Fcovers\u002Fobject-groupby-replace-reduce.png",4,"2026-07-14T08:16:56.309Z",27,{"id":57,"name":58,"username":59,"avatarUrl":40,"headline":60},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":63,"name":64},"javascript","JavaScript",[66,68,71,74],{"slug":63,"name":67,"color":40},"Javascript",{"slug":69,"name":70,"color":40},"webdev","Webdev",{"slug":72,"name":73,"color":40},"frontend","Frontend",{"slug":75,"name":76,"color":40},"node","Node",{"assessments":78},1,{"slug":34,"title":80},"Object.groupBy — group an array in one line",{"blocks":82,"version":78},[83,87,90,96,99,103,106,109,114,118,121,125,128,131,134,137,140,143,147,150,154,157,160,163,167,170,173,177,180,183,186,189,192,195,198,201,204,207,210,213,216,219,222],{"id":84,"html":85,"type":86},"b1","\u003Cp>Open any frontend codebase and search for \u003Ccode>.reduce\u003C\u002Fcode>. I&#39;ll wait.\u003C\u002Fp>","paragraph",{"id":88,"html":89,"type":86},"b2","\u003Cp>A meaningful fraction of those hits look like this:\u003C\u002Fp>",{"id":91,"code":92,"type":93,"language":94,"highlight":95},"b3","const byStatus = tasks.reduce((acc, task) => {\n  const key = task.status;\n  if (!acc[key]) acc[key] = [];\n  acc[key].push(task);\n  return acc;\n}, {});","code","js",[],{"id":97,"html":98,"type":86},"b4","\u003Cp>Or, if the author has been around long enough, this tighter variant:\u003C\u002Fp>",{"id":100,"code":101,"type":93,"language":94,"highlight":102},"b5","const byStatus = tasks.reduce((acc, task) => {\n  (acc[task.status] ??= []).push(task);\n  return acc;\n}, {});",[],{"id":104,"html":105,"type":86},"b6","\u003Cp>Both do the same thing: group an array by a key. It&#39;s one of the most common data transforms in any frontend codebase — group tasks by status, events by date, errors by code, users by role. You&#39;ve written it dozens of times because JavaScript never had a built-in name for it.\u003C\u002Fp>",{"id":107,"html":108,"type":86},"b7","\u003Cp>It does now. And it has been in every major browser since early 2024.\u003C\u002Fp>",{"id":110,"html":111,"text":112,"type":113,"level":31},"b8","\u003Ccode>Object.groupBy\u003C\u002Fcode> — the one-liner","Object.groupBy — the one-liner","heading",{"id":115,"code":116,"type":93,"language":94,"highlight":117},"b9","const byStatus = Object.groupBy(tasks, task => task.status);",[],{"id":119,"html":120,"type":86},"b10","\u003Cp>Pass an iterable and a callback. The callback returns the group key for each item. The result is a plain object where each key holds an array of the items that mapped to it.\u003C\u002Fp>",{"id":122,"code":123,"type":93,"language":94,"highlight":124},"b11","const tasks = [\n  { id: 1, status: 'done',        title: 'Ship it' },\n  { id: 2, status: 'todo',        title: 'Write tests' },\n  { id: 3, status: 'done',        title: 'Fix the bug' },\n  { id: 4, status: 'in-progress', title: 'Review the PR' },\n];\n\nconst grouped = Object.groupBy(tasks, t => t.status);\n\u002F\u002F {\n\u002F\u002F   done:         [{ id: 1, … }, { id: 3, … }],\n\u002F\u002F   todo:         [{ id: 2, … }],\n\u002F\u002F   'in-progress': [{ id: 4, … }],\n\u002F\u002F }",[],{"id":126,"html":127,"type":86},"b12","\u003Cp>The callback can return any value that stringifies to a useful key — a status enum, a date string, a category slug, a computed bucket. Items that map to the same string land in the same array. Items that would map to \u003Ccode>undefined\u003C\u002Fcode> land under the key \u003Ccode>&quot;undefined&quot;\u003C\u002Fcode>, which is a sign you should add a fallback in the callback.\u003C\u002Fp>",{"id":129,"html":130,"type":86},"b13","\u003C!-- playground:start -->",{"id":132,"html":133,"text":133,"type":113,"level":31},"b14","🎮 Try it yourself",{"id":135,"html":136,"type":86},"b15","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fposts\u002F2026-07-14-object-groupby-replace-reduce\u002Fplayground\u002F\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":138,"html":139,"type":86},"b16","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":141,"html":142,"type":86},"b17","\u003C!-- playground:end -->",{"id":144,"html":145,"text":146,"type":113,"level":31},"b18","When you need non-string keys: \u003Ccode>Map.groupBy\u003C\u002Fcode>","When you need non-string keys: Map.groupBy",{"id":148,"html":149,"type":86},"b19","\u003Cp>\u003Ccode>Object.groupBy\u003C\u002Fcode> coerces every key to a string. That covers most cases. If you need to group by something that isn&#39;t meaningfully stringifiable — an object reference, a boolean you want distinct from the string \u003Ccode>&quot;true&quot;\u003C\u002Fcode>, or any value where identity matters — \u003Ccode>Map.groupBy\u003C\u002Fcode> is the variant:\u003C\u002Fp>",{"id":151,"code":152,"type":93,"language":94,"highlight":153},"b20","const byPriority = Map.groupBy(tasks, task => task.priority > 5);\n\nbyPriority.get(true);  \u002F\u002F high-priority tasks\nbyPriority.get(false); \u002F\u002F everything else",[],{"id":155,"html":156,"type":86},"b21","\u003Cp>The result is a real \u003Ccode>Map\u003C\u002Fcode>, so you access values with \u003Ccode>.get()\u003C\u002Fcode> and iterate with \u003Ccode>.entries()\u003C\u002Fcode>. Keys are the exact values your callback returned — no stringification, no coercion, no surprises.\u003C\u002Fp>",{"id":158,"html":159,"text":159,"type":113,"level":31},"b22","A real-world example: the status board",{"id":161,"html":162,"type":86},"b23","\u003Cp>Before \u003Ccode>groupBy\u003C\u002Fcode>, building a Kanban-style board from a flat API response involved one of two patterns. Either a \u003Ccode>filter\u003C\u002Fcode> per lane:\u003C\u002Fp>",{"id":164,"code":165,"type":93,"language":94,"highlight":166},"b24","const lanes = ['todo', 'in-progress', 'review', 'done'];\nconst grouped = Object.fromEntries(\n  lanes.map(status => [status, tasks.filter(t => t.status === status)])\n);",[],{"id":168,"html":169,"type":86},"b25","\u003Cp>That walks \u003Ccode>tasks\u003C\u002Fcode> once per lane — four passes for four columns. Or a \u003Ccode>reduce\u003C\u002Fcode> — one pass, but four lines of bookkeeping per developer who reads it.\u003C\u002Fp>",{"id":171,"html":172,"type":86},"b26","\u003Cp>With \u003Ccode>Object.groupBy\u003C\u002Fcode>:\u003C\u002Fp>",{"id":174,"code":175,"type":93,"language":94,"highlight":176},"b27","const grouped = Object.groupBy(tasks, t => t.status);\nconst lanes = ['todo', 'in-progress', 'review', 'done'];\n\u002F\u002F `grouped` already has everything; `lanes` just controls render order",[],{"id":178,"html":179,"type":86},"b28","\u003Cp>One pass. No ceremony. The intent is in the function name.\u003C\u002Fp>",{"id":181,"html":182,"text":182,"type":113,"level":31},"b29","One subtle thing about the returned object",{"id":184,"html":185,"type":86},"b30","\u003Cp>\u003Ccode>Object.groupBy\u003C\u002Fcode> returns an object with a \u003Cstrong>null prototype\u003C\u002Fstrong> — \u003Ccode>Object.create(null)\u003C\u002Fcode>. It has no inherited \u003Ccode>toString\u003C\u002Fcode>, \u003Ccode>hasOwnProperty\u003C\u002Fcode>, or any of the usual \u003Ccode>Object.prototype\u003C\u002Fcode> methods. It&#39;s a pure data dictionary.\u003C\u002Fp>",{"id":187,"html":188,"type":86},"b31","\u003Cp>For most real uses this is exactly right: no prototype key collisions, no need for the \u003Ccode>hasOwnProperty\u003C\u002Fcode> dance before accessing a key. If downstream code needs a normal prototype — say, you&#39;re serializing it with a library that calls \u003Ccode>toString\u003C\u002Fcode> — wrap it: \u003Ccode>Object.assign({}, grouped)\u003C\u002Fcode>. But in practice you rarely need to.\u003C\u002Fp>",{"id":190,"html":191,"text":191,"type":113,"level":31},"b32","Support",{"id":193,"html":194,"type":86},"b33","\u003Cp>\u003Ccode>Object.groupBy\u003C\u002Fcode> is \u003Cstrong>Baseline 2024\u003C\u002Fstrong>: Chrome 117, Firefox 119, Safari 17.4, Node.js 21. If your targets include browsers from the last two years you can use it today without a polyfill. For older targets a one-liner shim is trivial, but at this point you&#39;re more likely to just set a browserslist target that excludes the handful of users still on pre-2024 releases.\u003C\u002Fp>",{"id":196,"html":197,"type":86},"b34","\u003C!-- quiz:start -->",{"id":199,"html":200,"text":200,"type":113,"level":31},"b35","🧠 Test yourself",{"id":202,"html":203,"type":86},"b36","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fquiz\u002Ftake.html?post=2026-07-14-object-groupby-replace-reduce\">Take the 6-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":205,"html":206,"type":86},"b37","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":208,"html":209,"type":86},"b38","\u003C!-- quiz:end -->",{"id":211,"html":212,"text":212,"type":113,"level":31},"b39","The takeaway",{"id":214,"html":215,"type":86},"b40","\u003Cp>\u003Ccode>Object.groupBy\u003C\u002Fcode> isn&#39;t a convenience — it&#39;s the word for what you&#39;ve been spelling out in four lines of \u003Ccode>reduce\u003C\u002Fcode>. Find the grouping reduces in your codebase, replace them, and delete whatever \u003Ccode>groupBy\u003C\u002Fcode> helper you wrote to avoid writing them inline. The operation has a name and a native implementation now; there&#39;s nothing left to carry.\u003C\u002Fp>",{"id":217,"type":218},"b41","divider",{"id":220,"html":221,"type":86},"b42","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":223,"type":224,"items":225,"ordered":18},"b43","list",[226,227,228,229,230],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","💼 \u003Cstrong>LinkedIn\u003C\u002Fstrong> — \u003Ca href=\"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F\">linkedin.com\u002Fin\u002Fparsa-jiravand\u003C\u002Fa>","✉️ \u003Cstrong>Email\u003C\u002Fstrong> (work &amp; contract inquiries): \u003Ca href=\"mailto:bestpractice2026@gmail.com\">bestpractice2026@gmail.com\u003C\u002Fa>","Open any frontend codebase and search for `.reduce`. I'll wait.\n\nA meaningful fraction of those hits look like this:\n\n```js\nconst byStatus = tasks.reduce((acc, task) => {\n  const key = task.status;\n  if (!acc[key]) acc[key] = [];\n  acc[key].push(task);\n  return acc;\n}, {});\n```\n\nOr, if the author has been around long enough, this tighter variant:\n\n```js\nconst byStatus = tasks.reduce((acc, task) => {\n  (acc[task.status] ??= []).push(task);\n  return acc;\n}, {});\n```\n\nBoth do the same thing: group an array by a key. It's one of the most common data transforms in any frontend codebase — group tasks by status, events by date, errors by code, users by role. You've written it dozens of times because JavaScript never had a built-in name for it.\n\nIt does now. And it has been in every major browser since early 2024.\n\n## `Object.groupBy` — the one-liner\n\n```js\nconst byStatus = Object.groupBy(tasks, task => task.status);\n```\n\nPass an iterable and a callback. The callback returns the group key for each item. The result is a plain object where each key holds an array of the items that mapped to it.\n\n```js\nconst tasks = [\n  { id: 1, status: 'done',        title: 'Ship it' },\n  { id: 2, status: 'todo',        title: 'Write tests' },\n  { id: 3, status: 'done',        title: 'Fix the bug' },\n  { id: 4, status: 'in-progress', title: 'Review the PR' },\n];\n\nconst grouped = Object.groupBy(tasks, t => t.status);\n\u002F\u002F {\n\u002F\u002F   done:         [{ id: 1, … }, { id: 3, … }],\n\u002F\u002F   todo:         [{ id: 2, … }],\n\u002F\u002F   'in-progress': [{ id: 4, … }],\n\u002F\u002F }\n```\n\nThe callback can return any value that stringifies to a useful key — a status enum, a date string, a category slug, a computed bucket. Items that map to the same string land in the same array. Items that would map to `undefined` land under the key `\"undefined\"`, which is a sign you should add a fallback in the callback.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fposts\u002F2026-07-14-object-groupby-replace-reduce\u002Fplayground\u002F)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## When you need non-string keys: `Map.groupBy`\n\n`Object.groupBy` coerces every key to a string. That covers most cases. If you need to group by something that isn't meaningfully stringifiable — an object reference, a boolean you want distinct from the string `\"true\"`, or any value where identity matters — `Map.groupBy` is the variant:\n\n```js\nconst byPriority = Map.groupBy(tasks, task => task.priority > 5);\n\nbyPriority.get(true);  \u002F\u002F high-priority tasks\nbyPriority.get(false); \u002F\u002F everything else\n```\n\nThe result is a real `Map`, so you access values with `.get()` and iterate with `.entries()`. Keys are the exact values your callback returned — no stringification, no coercion, no surprises.\n\n## A real-world example: the status board\n\nBefore `groupBy`, building a Kanban-style board from a flat API response involved one of two patterns. Either a `filter` per lane:\n\n```js\nconst lanes = ['todo', 'in-progress', 'review', 'done'];\nconst grouped = Object.fromEntries(\n  lanes.map(status => [status, tasks.filter(t => t.status === status)])\n);\n```\n\nThat walks `tasks` once per lane — four passes for four columns. Or a `reduce` — one pass, but four lines of bookkeeping per developer who reads it.\n\nWith `Object.groupBy`:\n\n```js\nconst grouped = Object.groupBy(tasks, t => t.status);\nconst lanes = ['todo', 'in-progress', 'review', 'done'];\n\u002F\u002F `grouped` already has everything; `lanes` just controls render order\n```\n\nOne pass. No ceremony. The intent is in the function name.\n\n## One subtle thing about the returned object\n\n`Object.groupBy` returns an object with a **null prototype** — `Object.create(null)`. It has no inherited `toString`, `hasOwnProperty`, or any of the usual `Object.prototype` methods. It's a pure data dictionary.\n\nFor most real uses this is exactly right: no prototype key collisions, no need for the `hasOwnProperty` dance before accessing a key. If downstream code needs a normal prototype — say, you're serializing it with a library that calls `toString` — wrap it: `Object.assign({}, grouped)`. But in practice you rarely need to.\n\n## Support\n\n`Object.groupBy` is **Baseline 2024**: Chrome 117, Firefox 119, Safari 17.4, Node.js 21. If your targets include browsers from the last two years you can use it today without a polyfill. For older targets a one-liner shim is trivial, but at this point you're more likely to just set a browserslist target that excludes the handful of users still on pre-2024 releases.\n\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 6-question quiz →](https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fquiz\u002Ftake.html?post=2026-07-14-object-groupby-replace-reduce)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n## The takeaway\n\n`Object.groupBy` isn't a convenience — it's the word for what you've been spelling out in four lines of `reduce`. Find the grouping reduces in your codebase, replace them, and delete whatever `groupBy` helper you wrote to avoid writing them inline. The operation has a name and a native implementation now; there's nothing left to carry.\n\n---\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)\n- 💼 **LinkedIn** — [linkedin.com\u002Fin\u002Fparsa-jiravand](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F)\n- ✉️ **Email** (work & contract inquiries): [bestpractice2026@gmail.com](mailto:bestpractice2026@gmail.com)",{"title":233,"canonical":234,"description":235},"You've been writing this `reduce` a hundred times. `Object.groupBy` do","https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fobject-groupby-replace-reduce","Grouping an array by a key is one of the most common data transforms in frontend code. You've been wiring it by hand with `reduce` for years. `Object.groupBy` is the native version","019fe660-2b2e-71fb-a0d2-00d6b11f0e71",{"id":238,"locked":18},"019fe660-322b-70fa-9272-70e8f963a6f1",[240],{"id":33,"slug":34,"title":36,"_count":241},{"questions":39},[243],{"locale":13,"slug":34},{"id":33,"slug":34,"title":36,"_count":245,"questionCount":39},{"questions":39}]