I was building a small console application. A simple cart. It was nothing fancy but a chance to experiment with input, calculations, and formatted output. Here’s the first version I wrote:
import java.util.Scanner;
public class Cart {
static void cart() {
Scanner scanner = new Scanner(System.in);
System.out.print("Enter item name: ");
String item = scanner.nextLine();
System.out.print("Enter price for each: ");
double price = scanner.nextDouble();
if (price <= 0) {
throw new IllegalArgumentException("Price must be positive.");
}
System.out.print("Enter quantity to buy: ");
int quantity = scanner.nextInt();
if (quantity <= 0) {
throw new IllegalArgumentException("Quantity must be positive.");
}
double total = price * quantity;
String itemLabel = quantity == 1 ? item : item + "s";
System.out.printf(
"You bought %d %s. Your grand total is KES %.2f.%n",
quantity, itemLabel, total
);
}
static void main(String[] args) {
cart();
}
}
When I ran it with something simple like apple:
Enter item name: apple
Enter price for each: 100
Enter quantity to buy: 2
The output was:
You bought 2 apples. Your grand total is KES 200.00.
Perfect, simple and predictable. It worked exactly as expected.
The First Spark Of Curiosity
But then I tried a word that wasn’t so straightforward:
Enter item name: box
Enter price for each: 100
Enter quantity to buy: 2
And the program printed:
You bought 2 boxs. Your grand total is KES 200.00.
I paused and thought I could maybe just add some rules:
Words ending in
xget appended withes. This would also apply fors,sh, andch.Words ending with
ybecomeies.But what about words like
mouseandchild?
It was exciting as it felt like executing the real craft of programming beyond just making something work.
First Attempt Towards A Smarter Solution
I decided to write a small helper function to handle the obvious cases:
static String pluralize(String word, int quantity) {
if (quantity == 1) return word;
if (word.endsWith("s") || word.endsWith("x") || word.endsWith("z")
|| word.endsWith("ch") || word.endsWith("sh")) {
return word + "es";
}
if (word.endsWith("y") && word.length() > 1
&& !"aeiou".contains("" + word.charAt(word.length() - 2))) {
return word.substring(0, word.length() - 1) + "ies";
}
return word + "s";
}
Now, I could run tests with:
System.out.println(pluralize("box", 2)); // boxes
System.out.println(pluralize("city", 2)); // cities
System.out.println(pluralize("class", 2)); // classes
System.out.println(pluralize("apple", 2)); // apples
System.out.println(pluralize("mouse", 2)); // mouses
The results were mostly correct; boxes, cities, classes but mouse to mouses stood out. Irregular nouns weren’t going to be solved by simple rules. And every new edge case made the code longer and more fragile.
This felt like the 'even numbers are hard' meme
At this point, I realized that my 'make it perfect' curiosity was bumping into the limitations of language itself.
Discovering ICU4J
Searching for solutions, I found ICU4J's Java library used in production for internationalization and pluralization and it looked promising. However after reading the documentation more carefully, I realized ICU4J doesn’t actually change the spelling of words. It decides when to use a singular versus plural form. For example, it can help you display:
You have 1 item
You have 2 items
But it does not automatically turn box into boxes or child into children. It solves the category problem, not the word-inflection problem.
The Tempting Idea
It was disappointing after realizing that rule-based pluralization was incomplete and that even production-grade libraries like ICU4J weren’t meant to pluralize raw nouns.
Just as I was about to give up, another thought crossed my mind:
What if I just ask an AI to do this?
It felt reasonable as modern AI models are excellent at language. They know that:
-
boxpluralizes toboxes -
classtoclasses -
mousetomice -
childtochildren
In theory, this meant making an API call, passing the noun, and getting back the correct plural. This guaranteed no grammar rules, and no irregular noun lists. Just letting the model handle the complexity.
For a moment, this felt like the perfect solution.
Why the AI Idea Actually Makes Sense (At First)
To be fair, this approach isn’t foolish. In fact, it has real strengths because an AI model:
- Understands irregular nouns
- Handles borrowed words and edge cases
- Adapts to language naturally
- Requires very little code on my end
For a small personal project, the idea is genuinely attractive. I wouldn’t need to maintain pluralization logic at all. I’d just delegate the problem to the AI via an API.
But then I paused and started thinking like a system designer, not just a coder.
The First Red Flag: Cost
Pluralization is a low-value, high-frequency operation. If every time a user adds an item to a cart the app needs to:
- Make a network request
- Pay for tokens
- Wait for a response
That cost adds up quickly. Paying for AI inference to decide whether to print boxes or items is hard to justify, especially when that same operation could be done locally with zero cost.
For small scripts or experiments, this might be fine. At scale, it becomes expensive very fast.
The Second Red Flag: Latency
Pluralization sits on the critical path of user feedback. It happens:
- While rendering UI
- While printing output
- During fast interactions
An AI call introduces:
- Network latency
- Timeout risk
- Retry logic
- Failure modes you don’t control (500 errors)
Something as simple as printing a sentence suddenly depends on the availability of an external service. That outright felt wrong.
The Third Red Flag: Non-Determinism
This was by far the biggest concern. Language is flexible, and AI reflects that. For example:
-
cactustocacti -
cactustocactuses
Both are correct but if the application prints:
You bought 2 cacti
and later:
You bought 2 cactuses
You just introduced inconsistency. An answer that is 'sometimes right' is often worse than a simpler one that is always consistent.
The Fourth Red Flag: Control and Safety
To pluralize a word using AI, I have to send user input to an external service. This raises the questions:
- What if the item name contains sensitive information?
- What if this data must stay on-device?
- What happens if the API changes behavior?
Suddenly, a simple console program is tied to:
- Network access -API keys
- Privacy considerations
At this point, the picture became clear. Opting to use AI for pluralization solves a hard linguistic problem but introduces cost, latency, inconsistency, dependency and still does not guarantee perfect results.
The Practical Tradeoff
At this point, I had to step back and ask myself what the real goal of my program is.
- Showing the number of items bought
- Showing the total cost
The exact spelling of the product name in plural wasn’t critical.
That’s when it clicked almost every app I use does the same thing. Shopping sites, notification systems, and dashboards don’t attempt to pluralize arbitrary nouns. They simply use a safe, controlled noun:
You bought 2 items
You have 5 notifications
Your cart contains 3 products
It is predictable, safe, and scales well since there are no edge cases to handle and also works perfectly if you later translate your application into other languages.
Final Code Snippet
Here’s how my original shopping cart code evolved after all this reflection:
double total = price * quantity;
String message = quantity == 1 ? "item" : "items";
System.out.printf(
"You bought %d %s. Your grand total is KES %.2f.%n",
quantity, message, total
);
Output:
You bought 1 item. Your grand total is KES 100.00.
You bought 2 items. Your grand total is KES 200.00.
No edge cases, no dictionary of irregular nouns, no fragile rules. This approach is exactly what large production systems do.
Lessons Learned
- Curiosity is important. Trying to improve the code teaches you a lot about programming, language, and tradeoffs.
- Language is messy and even simple pluralization can have dozens of edge cases.
- Production code values simplicity, correctness, and consistency.
- Using ICU4J helps in the right context and should be used for plural-aware messages and not for spelling every noun correctly.
- Opting for controlled vocabulary wins,is safe, predictable, and maintainable.
Thought
The journey from apple to apples to understanding why 2 boxes is rarely shown in apps is more than a lesson in pluralization. It is a lesson in designing software systems that works reliably, even when human language does not cooperate.
Sometimes, the simplest solution like displaying 2 items is also the most elegant.
Top comments (10)
I loved where this was going! I really enjoyed when you brought in the LLM. I thought I knew where you would end up, but I was wrong.
Why not use Redis, to capture your pluralizations?
Low latency, no duplication, still have control.
A local LLM or even SLM could be used also!
Thank you so much for writing this up! Keep up the good work!
Thank you very much. I appreciate you reading this and glad you enjoyed it.
I love how this highlights the little details that make code feel polished! Pluralization seems small, but it really improves user experience and readability. Thanks for sharing these practical insights!
Thank you very much. I appreciate your time to go through this and your insights as well.
Good one. This is quite refreshing. I am not a dev yet, but I was able to read your solution clearly and understand 100%.
Thank you very much. I appreciate your time to go through this and glad you found it easy to understand despite having a technical background.
This is really helpful.
Thank you very much. I appreciate your time to go through this and glad you liked it.
Technically funny 😁
Thank you very much. I appreciate your time to read the article and leave a comment.