Every language
14 implementations, copy-ready. One at a time with syntax highlighting, or all inline.
SQLSQLrunnable
SELECT type,
json_agg(item) AS items -- the group, kept AS A LIST
FROM items
GROUP BY type
ORDER BY type;GROUP BY collapses to aggregates — json_agg is the one that keeps the group AS A LIST (the actual grouping); SUM or COUNT would flatten it to a scalar. ORDER BY type makes the output stable; without it, group order is unspecified. Runs in the playground (SQLite aliases json_group_array as json_agg); MySQL spells it JSON_ARRAYAGG.
JSJavaScript
const byType = Object.groupBy(items, (item) => item.type);
// { bug: […], feature: […] } — ES2024, plain-object keys
const byId = Map.groupBy(items, (item) => item.id); // non-string keys keptNew in 2024 runtimes. Object.groupBy stringifies keys (fine for grouping by category, wrong for numbers you need back); Map.groupBy does not.
TSTypeScript
interface Item { type: 'bug' | 'feature'; title: string }
const byType = Map.groupBy(items, (i: Item): Item['type'] => i.type);
// Map<Item['type'], Item[]> — typed end to endAnnotate the callback's return so the Map's key type narrows; left inferred it widens to string.
GoGo
groups := make(map[string][]Item)
for _, item := range items {
groups[item.Type] = append(groups[item.Type], item)
}There is no stdlib helper — this 4-line loop IS the Go answer. map[string][]Item plus append composes the zero-value-nil-slice behavior for free.
RsRust
use std::collections::HashMap;
let mut groups: HashMap<&str, Vec<&Item>> = HashMap::new();
for item in &items {
groups.entry(item.kind).or_default().push(item);
}entry().or_default() is the idiom — it inserts the empty vec on first sight so push always works. itertools adds into_group_map() on top for iterator style.
PHPPHP
$groups = [];
foreach ($items as $item) {
$groups[$item['type']][] = $item;
}No array_group_by in the stdlang — the null-coalescing auto-vivification of $groups[...][] builds nested arrays on demand.
PyPython
from collections import defaultdict
groups = defaultdict(list)
for item in items:
groups[item['type']].append(item)
# itertools.groupby(items, key=...) ONLY groups consecutive runs —
# unsorted input gives you duplicate groups. Sort first if you use it.defaultdict(list) inserts the empty list on first access. The itertools.groupby consecutive-run trap is the #1 wrong answer copied off old blogs.
C++C++
#include <map>
#include <string>
#include <vector>
std::map<std::string, std::vector<Item>> groups;
for (const auto& item : items)
groups.try_emplace(item.type).first->second.push_back(item);try_emplace returns a pair (iterator, bool) — iterator->second is the group's vector, created empty on first sight, so push_back always works. std::map keeps keys sorted; std::unordered_map trades that order for speed.
C#C#
using System.Linq;
var groups = items
.GroupBy(i => i.Type)
.ToDictionary(g => g.Key, g => g.ToList());GroupBy is lazy (it returns IGrouping sequences, not a dictionary) — ToDictionary materializes it. ToLookup() is the built-in one-step variant with indexer access.
JvJava
import java.util.stream.Collectors;
Map<String, List<Item>> groups = items.stream()
.collect(Collectors.groupingBy(Item::type));groupingBy gives HashMap by default; pass TreeMap::new as a second argument for sorted keys, LinkedHashMap::new for first-seen order. toList() downstream for counts: counting().
SwSwift
let groups = Dictionary(grouping: items, by: { $0.type })
// [ItemType: [Item]]Dictionary(grouping:by:) is the one-liner. It crashed on duplicate keys in older Swift versions if you reached for Dictionary(uniqueKeysWithValues:) instead — grouping:by: is the safe constructor.
KtKotlin
val groups: Map<String, List<Item>> = items.groupBy { it.type }Stdlib one-liner. groupBy returns the first-seen key order; associate variants (associateBy) keep only the last item per key — a different operation.
RbRuby
groups = items.group_by { |item| item[:type] } # => { 'bug' => […] }Core method since 1.8.7 — returns a Hash of arrays in first-seen key order. Ruby had this before most languages had a name for it.
ZigZig
var groups = std.StringArrayHashMap(std.ArrayList(Item)).init(alloc);
for (items) |item| {
const gop = try groups.getOrPut(item.type);
if (!gop.found_existing) gop.value_ptr.* = std.ArrayList(Item).init(alloc);
try gop.value_ptr.append(item);
}StringArrayHashMap keeps first-seen insertion order (plain StringHashMap does not). getOrPut returns the single-lookup slot — the Zig spelling of entry().or_default().