This post is an AI translation of the Chinese original. If there are any discrepancies, the Chinese version takes precedence.
Introduction
When I build personal projects, my motivation is usually pretty straightforward: either I want to build something out of curiosity, or I need a small tool to make my day job a little easier.
Over the past couple of years, as AI coding assistants became an everyday part of my workflow, I noticed that the way I pick technologies started to shift. While tidying up some code recently, I took some time to step back and pin down a stable tech stack for my future side projects.
Moving On from Delphi and Java
Delphi: Fond Memories, but Sparse Training Data
I started my programming journey with Delphi. Even today, I still think Object Pascal is a wonderfully designed language. It doesn’t use the curly braces common in modern languages, but code organized with begin...end feels clean and easy to read.
Objectively, though, Delphi’s user base is tiny today. You have to write many basic libraries from scratch. More importantly in the AI era, there just isn’t much Delphi code in LLM training datasets. When you ask an AI to write Delphi, the output quality and speed are underwhelming. Much as I like the language, it simply doesn’t fit as a daily driver anymore.
Java: Great for Work, Too Heavy for Side Projects
At work, Java is the language I’ve spent the most time with. Its mature ecosystem and established conventions make it a reliable choice for enterprise development.
For personal projects, though, the drawbacks stand out:
- Too much ceremony: Even for a simple feature, you have to deal with boilerplate, complex package structures, and config files. It slows you down.
- Resource heavy: Build artifacts are bulky, and the JVM takes up a fair amount of memory at runtime. I run a cheap personal VPS with limited resources, and hosting a few Java services quickly becomes a burden.
Because of this, I try to avoid Java outside of work.
Front-End: Why I Skip Modern Frameworks
For web development, I stay away from mainstream frameworks like React or Vue.
There are two main reasons:
First, I’ve never been particularly strong with front-end work. On top of that, modern front-end frameworks and build tools change constantly. If you step away for a few months, half the concepts have evolved. Keeping up with that churn just isn’t worth the effort for me.
Second, the Node ecosystem tends to leave behind bloated node_modules folders. Once you accumulate several small tools, those dependencies eat up gigabytes of disk space and become annoying to manage.
When all I need is a simple interface for personal use, bringing in that entire toolchain feels counterproductive.
The Core Choice: C# and .NET
For back-end work and tools with an interface, I’ve centered my stack around C# and .NET.
I’ve always appreciated C#‘s design. It moves fast when adopting useful language features—like LINQ, pattern matching, records, and nullable reference types. It’s probably no coincidence that the creator of Delphi and C# is the same person, Anders Hejlsberg; there’s a familiar elegance in how both languages feel.
Modern .NET also handles cross-platform well. I’ve built a few utilities on Linux Mint and Windows using C#. With Native AOT, you get a standalone binary around 10–20 MB that boots up instantly and uses minimal memory. It runs comfortably on a low-end VPS or a local laptop.
For web UIs, I lean toward C# Minimal APIs + HTMX + Tailwind CSS:
- C# handles business logic and endpoints, returning rendered HTML fragments directly.
- The front end uses plain HTML, relying on HTMX attributes for partial page updates. That’s plenty for my needs.
- The entire setup requires no Node runtime, no TypeScript, and no dependency rabbit holes.
Practical Choices by Scenario
Here is how I break down my stack across different types of projects:
1. CLI and Scripting Tools: Go
When writing command-line tools, background workers, or scraping scripts, Go is my top pick.
- It compiles fast, and the single binary is easy to share and run anywhere.
- The syntax is consistent and predictable. AI models write Go accurately, and when the compiler throws an error, the AI usually fixes it on the first try.
2. Desktop Apps: Windows Native vs. Linux Trade-Offs
I split my time between Windows and Linux Mint:
- Windows: My first choice is native C# (WPF or WinUI). It fits right into the OS and the tooling is solid.
- Linux Mint:
- Currently sticking with Electron: While packaging a 100 MB+ bundle for a simple utility feels wasteful, Electron still offers the most reliable compatibility on Linux desktops. When weird windowing or desktop environment issues pop up, solutions are readily available, and AI tools know the stack inside out. For now, I accept the size penalty for development speed.
- Looking ahead to PyQt: I’m not comfortable with C++, and writing Linux GUI code in C++ is a tall order. If I want a lighter, truly native GUI on Linux down the road, PyQt is my most realistic path.
3. Mobile Apps: Native Android
I rarely build mobile apps, and without any iOS devices, cross-platform support isn’t a priority.
I gave Flutter a try earlier and built a few small apps with good results. Given my current needs, though:
- First choice: Native Android (Kotlin + Jetpack Compose). Thanks to my Java background, picking up Kotlin felt natural. Working with native system permissions and background services is straightforward, and AI assistants handle Compose code well.
- Backup plan: If I ever need to target multiple platforms in the future, I can always revisit Flutter.
Working with AI: Real-World Constraints
Beyond language preferences, the AI tools and model tiers I rely on directly influence my choices.
Here is my setup:
- At work: I use GitHub Copilot Business, with access to flagship models from OpenAI and Anthropic.
- For personal projects: I use opencode, subscribed to opencode go.
For personal development, I work within standard model quotas rather than burning expensive flagship tokens on every turn. That means my stack needs to meet two practical requirements:
- Abundant training data: Go, C#, and Kotlin are heavily represented in open-source repositories. AI assistants understand their idioms and rarely hallucinate strange patterns.
- Clear compiler feedback: When working with standard models, debugging runtime errors in dynamic languages can be frustrating. Go, C#, and Kotlin are all strongly typed and compiled. Their compiler errors are clear enough for AI to fix issues right away.
Storage and Operations
Outside of the code itself, I keep the runtime environment as simple as possible:
- Storage: Embedded SQLite or DuckDB. Data lives in a single file, eliminating the need to run a standalone database server. Backups are just a file copy.
- Runtime: Lightweight Docker Compose setups using minimal base images. Deploying and tearing down services is clean and easy.
- Build scripts: A simple
build.shon Linux andbuild.ps1on Windows to capture common build and test commands.
Wrap-Up
Putting it all together, my current personal stack looks like this:
- System tools & CLI: Go
- Web services & tools: C# (.NET + HTMX)
- Desktop: Native C# on Windows; Electron on Linux Mint for now (with PyQt on the horizon)
- Mobile: Native Android (Kotlin)
This isn’t a trendy or cutting-edge setup, but it suits my taste in languages, spares me from front-end build pipelines I don’t want to maintain, and works well with my AI tooling. Building things quickly with minimal fuss—and without having to babysit them later—is exactly what I want from my personal stack.