
I can’t go into details, but those of you who know your algorithms know how big a deal this is. Better still, the client’s happy.

I can’t go into details, but those of you who know your algorithms know how big a deal this is. Better still, the client’s happy.
A couple of weeks back, I wrote about how coding happens on a spectrum whose opposite ends are:
I myself have been writing code for different purposes, on different parts of this spectrum (see the diagram at the top of this article for where they land on the spectrum):
If we have a term like “vibe coding,” where you build an application by describing what you want it to do using natural language (like English) and an LLM generates the code, we probably should have an equal opposite term that’s catchier than “traditional coding,” where you build an application using a programming language to define the application’s algorithms and data structures.
I propose the term grind coding, which is short, catchy, and has the same linguistic “feel” as vibe coding.
Having these two terms also makes it clear that there’s a spectrum between these two styles. For instance, I’ve done some “mostly grind with a little vibe” coding where I’ve written most of the code and had an LLM write up some small part that I couldn’t be bothered to write — a regular expression or function. There’ve also been some “most vibe with a little grind” cases where I’ve had an LLM or Claude code do most of the coding, and then I did a little manual adjustment afterwards.
Because some people asked, and because I’m going to be busy for the next day (I’ll explain later), here are more shots from recently-added pages to my notebook. These are notes on RAG and LangChain, taken and condensed from a couple of books, a couple of online sources, and my own experimenting with code. Enjoy!
I remember cringing at this one line from an episode of the 1990s TV series, Star Trek: Voyager:
Computer, install a recursive algorithm!
I always thought that you would never program a computer that way…until now.
A lot of the drudgery behind assembling the “Tampa Bay Tech Events” list I post on this blog every week is done by a Jupyter Notebook that I started a few years ago and which I tweak every couple of months. I built it to turn a manual task that once took the better part of my Saturday afternoons into a (largely) automated exercise that takes no more than half an hour.
The latest improvement was the addition of AI to help with the process of deciding whether or not to include an event in the list.
In the Notebook, there’s one script creates a new post in Global Nerdy’s WordPress, complete with title and “boilerplate” content that appears in every edition of the Tech Events list.
Then I run the script that scrapes Meetup.com for tech events that are scheduled for a specific day. That script generates a checklist like the one pictured below. I review the list and check any event that I think belongs in the list and uncheck any event that I think doesn’t belong:

In the previous version of the Notebook, all events in the checklist were checked by default. I would uncheck any event that I thought didn’t belong in the list, such as one for real estate developers instead of software developers, as well as events that seemed more like lead generation disguised as a meetup.
The new AI-assisted version of the Notebook uses an LLM to review the description of each event and assign a 0 – 100 relevance score and the rationale for that score to that event. Any event with a score of 50 or higher is checked, and anything with a score below 50 is unchecked. The Notebook displays the score in the checklist, and I can click on the “disclosure triangle” beside that score to see the rationale or a link to view the event’s Meetup page.
In the screenshot below, I’ve clicked on the disclosure triangle for the Toastmasters District 48 meetup score (75) to see what the rationale for that score was:

For contrast, consider the screenshot below, where I’ve clicked on the disclosure triangle for Tampa LevelUp Events: Breakthrough emotional eating with Hyponotherapy. Its score is 0, and clicking on the triangle displays the rationale for that score:

One more example! Here’s Tea Tavern Dungeons and Dragons Meetup Group, whose score is 85, along with that score’s rationale:

I don’t always accept the judgement of the LLM. For example, it assigned a relevance score of 40 to Bitcoiners of Southern Florida:

Those of you who know me know how I feel about cryptocurrency…

…but there are a lot of techies who are into it, so I check the less-scammy Bitcoin meetups despite their low scores (there are questionable ones that I leave unchecked). I’ll have to update the prompt for the LLM to include certain Bitcoin events.
Speaking of prompts, here’s the cell in the Notebook where I define the function that calls the LLM to rate events based on their descriptions. You’ll see the prompt that gets sent to the LLM, along with the specific LLM I’m using: DeepSeek!

So far, I’m getting good results from DeepSeek. I’m also getting good savings by using it as opposed to OpenAI or Claude. To rate a week’s worth of events, it costs me a couple of pennies with DeepSeek, as opposed to a couple of dollars with OpenAI or Claude. Since I don’t make any money from publishing the list, I’ve got to go with the least expensive option.
It’s always a treat to see one of Dr. Venkat Subramaniam’s presentations, and Monday evening’s session, Identifying and fixing Issues in Code using AI-based tools, was no exception!
On behalf of the Tampa Bay Artificial Intelligence Meetup, Anitra and I would like to thank Ammar Yusuf, Tampa Java User Group, and Tampa Devs for inviting us to participate in this meetup, and to thank Venkat for an excellent lecture.
Venkat described a specific phenomenon regarding how we perceive AI’s competence:
Venkat demonstrated several scenarios where code looked correct but had problems that weren’t immediately apparent, and showed how AI helped (or didn’t).
The first case study was a version of a problem presented to Venkat by a client. He couldn’t present the actual code without violating the client NDA, so he presented a simplified version that still captured the general idea of the problem with the code.
Here’s the first version of the code:
// Java
import java.util.*;
public class Sample {
public static List stringsOfLength5InUpperCase(List strings) {
List result = new ArrayList<>();
strings.stream()
.map(String::toUpperCase)
.filter(string -> string.length() == 5)
.forEach(result::add);
return result;
}
public static void main(String[] args) {
var fruits = List.of("Apple", "Banana", "Orange", "Grape", "Guava", "Kiwi",
"Mango", "Nance", "Papaya", "Peach", "Lime", "Lemon");
var result = stringsOfLength5InUpperCase(fruits);
System.out.println(result);
}
}
This version of the code works as expected, printing the 7 fruit names in the list that are 5 characters long.
Right now, it’s single-threaded, and it could be so much more efficient! A quick change from .stream() to .parallelStream()should do the trick, and the resulting code becomes
// Java
import java.util.*;
public class Sample {
public static List stringsOfLength5InUpperCase(List strings) {
List result = new ArrayList<>();
// Here's the change
strings.parallelStream()
.map(String::toUpperCase)
.filter(string -> string.length() == 5)
.forEach(result::add);
return result;
}
public static void main(String[] args) {
var fruits = List.of("Apple", "Banana", "Orange", "Grape", "Guava", "Kiwi",
"Mango", "Nance", "Papaya", "Peach", "Lime", "Lemon");
var result = stringsOfLength5InUpperCase(fruits);
System.out.println(result);
}
}
The code appears to work — until you run it several times and notice that it will occasionally produce a list of less than 7 fruit names.
Why did this happen? Because Java’sArrayList isn’t thread-safe, and writing to a shared variable from inside a parallel stream causes race conditions. But this is the kind of bug that’s hard to spot.
Venkat fed the code to Claude and asked what was wrong with it, and after a couple of tries (because AI responses aren’t consistent), it identified the problem: creating a side effect in a stream and relying on its value. It suggested using a collector like toList() to capture the the 5-character fruit names; it’s thread-safe.
Claude also suggested applying the filter before converting the list values to uppercase, so as not to perform work on values that would be filtered out.
The takeaway: AI is excellent at spotting errors that we humans often miss because we’re so focused on the business logic.
I didn’t get a photo of this code example, but it featured a function that looked like this:
public String doSomething(String someValue) {
// Some code here
someValue = doSomethisElse(someValue)
// More code here
}
I’m particularly proud of the fact that I spotted the mistake was the first one to point it out: mutating a parameter.
Venkat fed the code to Claude, and it dutifully reported the same error.
It was easy for me to spot such an error in a lone function. But spotting errors like this in an entire project of files? I’d rather let AI do that.
I didn’t get a photo of this one, but it featured base class CurrencyConverter with a method convert(float amount). A subclass NokConverter attempted to override it to handle Norwegian Krone.
The problem was that NokConverter’s conversion method’s signature was convert(int amount), which meant that it was overloaded instead of overridden. As a result, polymorphism was lost, and the client code ends up calling the base class method instead of the subclass method. But that’s pretty easy to miss — after all, the code appears to work properly.
A quick check with the AI pointed out that the method was not actually overriding, and it also suggested adding the @Override annotation, which is meant to prevent this kind of subtle error.
Remember: don’t just let AI fix it; understand why the fix works. In this case, it was about strictly enforcing contract hierarchy.
Venkat asked Claude to write a Wordle clone, and it did so in seconds.
But: the logic regarding how yellow/green squares were calculated was slightly off in edge cases.
AI sometimes implements logic that looks like the rules but fails on specific boundary conditions. It’s a good idea to write unit tests for AI-generated logic. Never trust that the algorithmic logic is sound just because the syntax is correct.