HomeTechnicalI Vibe Coded A Regex Engine

I Vibe Coded a Regex Engine (Or did I?)

Published Aug 16, 2025
Updated Aug 16, 2025
7 minutes read

Intro

I've been using AI a lot for research and debugging. I've used tools like v0 here and there for both production and brainstorming, but I still write almost all my code manually.

I enjoy writing code, typing out a bunch of code gives me a lot of satisfaction.

Today, I found out that Codecrafters’s build your own grep is free this month. So, I decided to take on the challenge and try vibe coding for the first time.

In this post, I'll share my experience, discuss the regex engine I built, the tools I used, and what I liked and didn't like about vibe coding.

Vibe coding VS AI-assisted programming

My definition of vibe coding is using Gen-AI to create code without worrying about the outcome, as long as it works.

Vibe coding is not the same thing as writing code with the help of LLMs!

— Simon Willison

I won't explain the definition of vibe coding here, but I highly recommend reading Not all AI-assisted programming is vibe coding (but vibe coding rocks) to understand the difference between vibe coding and AI-assisted programming.

My goal was to try "vibe coding" using Zed Agent Panel and Claude Sonnet 4, but it immediately turned into AI-assisted programming.

I couldn't resist looking at the code, making changes, and asking Claude to adjust things, even though the implementation was working as expected. I wanted the AI to do it my way and create the code I would have written!

So, this quickly became a fun pair programming session with Claude, rather than a true vibe coding session.

Prompting and outlining the codebase

My main approach to interacting with an LLM is to give as much context and detail as possible. Before sending the prompt, I ask myself if a real human could understand what needs to be done and what the requirements are just by reading that prompt.

I did the same thing with the AI agent here. Codecrafters provides minimal instructions for each step of the project. These instructions are simple enough to give you a hint about what needs to be done and what the expected output is for that step, but you have to do the implementation yourself.

This significantly made the entire experience more enjoyable. I decided to merge 2 or 3 instructions, add context and outlines, and suggest an approach to the agent, then let the agent write the code for me.

Crafting the initial prompts

A Codecrafters instruction looks like this:

The image shows a task from Codecrafters.
The image shows a task from Codecrafters.

For example, my first prompt was simply merging three of these instructions into one and adding some hints and adjustments:

Implement a regex engine from scratch using typescript without using built-in regex features. The program should read input from stdin and accept a regex pattern as an argument with the -E flag. It must exit with status code 0 if the pattern matches any part of the input string and 1 if it does not. The implementation should support the following features:
1. Digit Character Class (\d)
 
Support the \d character class, which matches any digit (0-9).
Example: \d should match "3" in "apple123" but not "c" in "apple".
Execution example: echo -n "apple123" | ./your_program.sh -E "\d"
Exit with 0 if a digit is found, 1 if not.
 
2. Word Character Class (\w)
 
Support the \w character class, which matches any alphanumeric character (a-z, A-Z, 0-9) and underscore (_).
Example: \w should match any character in "foo101" but not in "$!?".
Execution example: echo -n "alpha_num3ric" | ./your_program.sh -E "\w"
Exit with 0 if a word character is found, 1 if not.
Note: Underscore (_) is included as it’s considered part of a word in programming identifiers (e.g., variable and function names).
 
3. Positive Character Groups
 
Support positive character groups, which match any character present within a pair of square brackets (e.g., [abc]).
Example: [abc] should match any character in "apple" (a, p, or l) but not in "dog".
Execution example: echo -n "apple" | ./your_program.sh -E "[abc]"
Exit with 0 if any of the specified characters are found, 1 if not.
 
Additional Notes
 
The program must be a shell script that processes input from stdin and takes the regex pattern as an argument (e.g., ./your_program.sh -E "<pattern>").
Do not use built-in regex features (e.g., TypeScript or shell regex tools like grep); implement the matching logic manually.
Ensure the program handles the input and pattern correctly, exiting with the appropriate status code based on whether a match is found.

I suspect my prompt structure isn't very good since I haven't used AI to write code much. However, Claude executed the code extremely well. The code worked exactly as expected, followed best practices, and everything was in order, but there was a minor issue!

Not quite my tempo!

The only problem was that the code structure wasn't exactly how I wanted it!

Not quite my tempo!

Unfortunately, I didn't commit the early code since I wasn't planning to write this post. However, because I was using TypeScript to build this project, Claude naturally took a functional approach and created several files and different functions to achieve it's objective.

I don't have anything against functional programming, and I know it's the preferred approach for most JS/TS developers. However, I personally like to group all related functions within one class and then use the class to call its methods.

I typically have a few files with a huge chunk of code, each containing a class with several methods. Then, whenever I need a method, I just need to remember which class it belongs to, and from there, I can chain the methods together.

I usually add JSDocs to the mix, which makes it even better. This approach works extremely well when you’re dealing with a large team. I often found myself dealing with bunch of functions with similar functionalities within the same codebase, implemented by different people and teams, simply because they had no idea that such functionality had been implemented before.

For example, you might find getSession, getUserSession, and getCurrentSession within the same codebase, all aiming to retrieve the user’s session.

By scoping user into its own class, you would have something like user.getSession. Then, whenever someone wants to interact with the user entity, they just need to check the methods on the user instance to see if that functionality is already implemented or not.

I wanted to do the same with this regex engine. Instead of adjusting the prompt, I decided to outline the project myself, create the directories, and set up the initial classes.

This way, I could set up the playground for the agent so it better understands the framework I have in mind.

I wanted only two modules within my codebase.

.
├── main.ts
└── tokenizer.ts

The tokenizer module is a utility class, and all the functions related to tokenizing user input go within this module.

The main module handles the business logic.

I wrote the initial code for tokenizing the user input myself. I handled the first ~10 steps of Codecrafters' challenge with minimal help from the LLM. By the time I finished these initial steps, I had a clear outline of how I wanted things to proceed.

From there, I started prompting again, and in most cases, Claude one shotted em perfectly on the first try.

Although I reviewed the code at every step and made some adjustments, but these were very minor, and Claude's output was clean and straightforward.

Codecrafters' unit tests were also extremely helpful to ensure everything stayed on track. I often asked Claude to run the tests, and I also ran them manually quite frequently.

Custom engine VS Grep

From there, it took a few hours of prompting and adjusting to reach the end. After a few hours, I had a solid and fully functional regex engine.

The code quality was impressive imo, but I still spent a lot of time creating prompts and adjusting the code myself. I realized that the more context and examples I gave the agent, the better it performed, even though I didn't use any specific prompting technique.

During this process, I tried a bunch of different LLMs through OpenRouter, like Qwen3 Coder, but none of them came close to Claude based on this limited experiment.

In the end, I asked Claude to create a bunch of test directories with mock log files so I could test the regex engine and compare it with the grep output.

The outputs were identical in all the test cases I ran. you can see a few of the test cases in the screenshot below:

The image shows the regex engine outputs in in the terminal.
Custom engine vs Grep

Great, by this point, I was confident that I had a solid regex engine, and Claude executed it perfectly as far as I could tell.

So I pushed the final commit & completed the challenge, and left my laptop to grab something to eat.

While this experiment was fun, something still felt off. The more I thought about it, the more I realized why I felt a bit off and unsatisfied with the overall time I spent on this project.

Here are my final thoughts.

The good, the bad and the ugly

I estimate it would take me about 100 hours to build this regex engine with such precision from scratch, but with Claude, I managed to complete it in 5 or 6 hours, which is impressive.

However, there is still a strong sense of unfulfillment in the overall experience. But why is that?

When you build something from scratch, with your own sweat, and tears, you feel a sense of pride and achievement when you see the final result.

You will learn the hard lessons that become ingrained in your brain, making you more effective overall.

I believe that's generally how you develop intuition. It enables you to make the best decisions with much less effort because you can easily recognize patterns and avoid common mistakes.

The most practical engineering techniques I know, I learned by doing, breaking things, and making mistakes repeatedly until I learned my lessons.

These experiences aren't easily transferable. You can read about them in books and watch others discuss em, but you won't truly understand until you experience setbacks yourself and finally learn the lesson.

Even Cus D'Amato couldn't teach you boxing, no matter how talented and bright you are, until you put in hundreds of hours in the gym and get punched in the face repeatedly. You could read about the technique and watch others do it, but that's not going to help you.

The reward is the feeling you get when you finally knock out your opponent in the ring during a tough fight.

I don’t think you’ll ever get that knockout feeling from doing something with AI. I spent a few hours on this project, and in the end, I didn’t learn much except how to write better prompts.

Still, I have the output I wanted and needed, and it was kind of fun, but it wasn't a fulfilling experience.

The good part is that now I can do things much faster if I need to by using an AI agent.

The bad part is that I won't get the same fulfillment I get from writing a piece of software myself when I let an AI agent do it.

The ugly part is that I found myself distracted, browsing YouTube and X while the agent was working in the background, and I never reached a flow state while I was “vibe coding”.

Overall, I will definitely try this again, and I'll try to use LLMs to build side projects that I never found the time to actually do.

Source code

💻 Here's the source code: View on Github