Introduction
When we tell a client that Scala is our default for backend and data-intensive systems, the questions come back fast: why such a niche language, what does it actually buy us, and is it worth the investment? So we are writing a series of short posts, one language feature at a time, each compared against the more popular alternatives.
Disclaimer: It is not our intention to trash Java, TypeScript or Python (we use them too, depending on the job). But we came to realize that quite often, while building a client's solution, we were saying things like "This would have been less convoluted in Scala", or "This class of errors wouldn't have happened at all, with Scala's type system".
We start with the part that is easiest to see, and hardest to argue with: expressiveness and conciseness. Most of what makes Scala's other features worth having (pattern matching, algebraic data types, for-comprehensions) comes back to the same root idea. Say what you mean, and let the compiler work out the rest.
Problem: sort customers by highest order amount
Take an unglamorous task: given a list of orders, group them by customer, keep the customers with more than one order, and return their total spend, sorted highest first. Here is Python.
from collections import defaultdict
def top_repeat_customers(orders):
totals = defaultdict(float)
for order in orders:
totals[order["customer"]] += order["amount"]
counts = defaultdict(int)
for order in orders:
counts[order["customer"]] += 1
repeat = {c: t for c, t in totals.items() if counts[c] > 1}
return sorted(repeat.items(), key=lambda kv: kv[1], reverse=True)Java, even with modern Streams:
Map<String, Double> topRepeatCustomers(List<Order> orders) {
Map<String, List<Order>> byCustomer = orders.stream()
.collect(Collectors.groupingBy(Order::customer));
return byCustomer.entrySet().stream()
.filter(e -> e.getValue().size() > 1)
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().stream().mapToDouble(Order::amount).sum()
))
.entrySet().stream()
.sorted(Map.Entry.<String, Double>comparingByValue().reversed())
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue,
(a, b) -> a, LinkedHashMap::new
));
}TypeScript:
function topRepeatCustomers(orders: Order[]): [string, number][] {
const byCustomer = new Map<string, Order[]>();
for (const order of orders) {
const list = byCustomer.get(order.customer) ?? [];
list.push(order);
byCustomer.set(order.customer, list);
}
return [...byCustomer.entries()]
.filter(([, os]) => os.length > 1)
.map(([c, os]) => [c, os.reduce((sum, o) => sum + o.amount, 0)] as [string, number])
.sort((a, b) => b[1] - a[1]);
}And Scala:
def topRepeatCustomers(orders: List[Order]): List[(String, Double)] =
orders
.groupBy(_.customer)
.view
.filter(_._2.size > 1)
.mapValues(_.map(_.amount).sum)
.toList
.sortBy(-_._2)Same input, same output, four languages. The Scala version is roughly a third the length of the Java one, and it reads as a single sentence: group the orders by customer, filter the ones with more than one order, map the values (aka the orders for each customer) into the sum of their amounts, transform this Map into a List, and sort it. There's no trick here either: groupBy, filter, and sortBy all exist in some form in every language above. What differs is that Scala never makes you hand-roll a loop over a collection, keep two temporary maps in sync, re-derive a comparator, or pick between five overloads of collect.
Conciseness isn't code golf
We should be precise about the claim, because "fewer characters" on its own is a bad goal. Every language has unreadable one-liners, Scala included if you go looking. What we care about is the signal-to-noise ratio: how much of the code on screen is business logic, and how much is ceremony the language demands no matter what you are trying to say.
In the Java example, over half the lines are about how to iterate and collect (Collectors.toMap, LinkedHashMap::new, a merge function we don't actually need but the overload requires anyway), not what we're computing. TypeScript has the same problem in a different shape: manual Map bookkeeping, because arrays don't ship a built-in groupBy. Scala's standard library just gives you the vocabulary: groupBy, filter, mapValues, sortBy, and plenty more, so the code you write reads like the sentence you would use to describe the problem out loud.
There is a practical payoff too. The less machinery we type by hand, and the more we lean on what the language already ships, the fewer places a bug has to hide.
Why this matters more with agentic coding
Every AI assistant works inside a finite context window. Every token it spends re-deriving Java's Collectors incantations, or TypeScript's manual Map plumbing, is a token it is not spending on your business logic or the three other files that logic touches.
So a codebase where the same operation takes four lines instead of twelve costs the agent less to read, leaves more room for your actual architecture, and gives it less boilerplate in which to quietly plant a bug nobody was watching. We have found AI-generated Scala needs fewer correction rounds than AI-generated Java for exactly that reason. There is less machinery to get subtly wrong.
