← Back to Blog
AI-Assisted Development10 min read

Coding an App with ChatGPT: How I Actually Use It

Over the last few months I have gradually started using ChatGPT more seriously while working on my own applications. I had already spent quite a bit of time building Pocket Predictor, and lately I have been experimenting with new app ideas as well. Somewhere along the way, AI stopped feeling like an interesting tool I occasionally asked programming questions and started becoming part of the way I actually write software.

What surprised me most is not that it can produce code. Code generation itself is obviously useful, but the bigger difference is how much shorter the distance becomes between having an idea and seeing something working. There are plenty of things I could write myself, but doing so might mean reading documentation, checking examples, remembering some syntax I have not used for a while and spending an hour getting all the small details right. Now I can often explain what I am trying to achieve, get a reasonable implementation, understand it, adapt it and continue from there.

The setup is still very primitive. I use WebStorm for development and keep ChatGPT open separately in the browser. My MacBook is an older Intel model, so the desktop ChatGPT app is not even available to me. There is no direct connection between the conversation and my project, no repository access and no automatic understanding of what files exist. If I want AI to know something about my application, I have to bring that information into the chat myself.

So most of my AI-assisted development is basically a conversation taking place between two windows.

What the workflow actually looks like

When I want to build a feature, I usually start by describing what I want the application to do. If there is already related code, I paste the relevant file or files and explain how the current version works. Then I ask for the change, copy the proposed code back into WebStorm and run the application.

If everything works, great. More often, something is slightly wrong, an error appears or the solution does not fit the existing project as well as it looked like it would. In that case I bring the result back into the conversation, paste the error or explain what went wrong, and we continue from there.

The process is usually something like:

  1. Describe the feature.
  2. Provide the relevant code.
  3. Ask for the implementation or modification.
  4. Copy it into the project.
  5. Run the application.
  6. Bring the error or unexpected behaviour back into the conversation.
  7. Adjust the solution.
  8. Repeat until it works.

It sounds almost comically manual when written out like that, and it is. I am effectively acting as the bridge between the AI and the codebase. But even in this form, it already saves enough time that I prefer working this way for many tasks.

The process becomes much less convenient once the application grows. A small component is easy: paste one file, explain the change and see what happens. In a larger project, one screen might depend on several components, some shared state, a service and a couple of utility functions. None of this exists from the model’s point of view unless I explicitly provide it.

When I start a fresh conversation, I sometimes end up spending quite a while rebuilding the project context. I explain what the app does, how the relevant parts are connected and what I am currently trying to change. Then I paste the contents of several important files. If there is already a lot of code, I may copy most of the relevant part of the project into the new chat before asking the real question.

At that point my requests become very specific. Instead of simply saying “add this feature”, I often ask which file should be changed, what exact block should be replaced and where a new piece of code should be inserted. The more precise the context, the better the result tends to be. If important information is missing, the model fills in the gaps itself, and those assumptions are often where things start going wrong.

I think of it as a pair programmer

The closest description I have found for how I use it is a pair programmer combined with a very fast code generator.

I still decide what the application should do, how a feature should behave and whether the proposed solution makes sense. The AI helps me move through the implementation faster. Sometimes I want a complete component. Other times I only need a small modification to code that already works. Quite often the most useful response is not a huge rewritten file, but a precise explanation of what to change in the existing one.

That is important because I do not really want to replace my understanding of the project with generated code. In most cases I could write the same thing myself. The difference is that doing it from scratch might take considerably longer.

There is a lot of work in software development that is not especially intellectually difficult but still consumes time. You know roughly how something should work, but you need to look up the correct API, find the right syntax, remember how a library expects a particular option to be configured or work through an example before adapting it to your own project.

This is where the time saving becomes very noticeable.

It is also useful when I am dealing with something I have not worked with much before. Instead of stopping everything to study an entire technology before I can continue, I can start from the concrete problem I have right now. I ask how that problem could be solved, inspect the answer, test it and learn the relevant parts while implementing the feature.

I still want to understand what I put into the codebase. Maybe I would not have written the exact same implementation from memory, but I should at least understand what it is doing and why. Otherwise it becomes very easy to build several layers of generated code on top of each other and eventually end up with a project I can no longer debug myself.

For me, the benefit is not that AI makes understanding code unnecessary. It is that I spend less time producing every piece of it manually.

The first answer is often not the useful one

One of the most common complaints I hear about AI coding is that it writes nonsense. That criticism is not difficult to understand. It absolutely does.

It can misunderstand the project, use an API incorrectly, suggest something that simply does not compile or produce a solution that technically works but makes no sense in the context of the rest of the application. Sometimes it fixes the problem I asked about and creates another one somewhere else.

What I have learned, though, is not to treat the first answer as the final verdict on whether AI can solve the problem.

When a proposed solution fails, I keep the conversation going. I paste the real error, explain what happened and ask it to reconsider. If the answer is based on a wrong assumption, I correct that assumption. If the approach itself looks bad, I ask for another one. Sometimes I reformulate essentially the same problem several times before I get a solution I am happy with.

I think this is one of the biggest differences between using AI effectively and trying it once and deciding it is useless.

If I ask a person a question and they misunderstand what I mean, I do not normally assume that they are incapable of helping me. I explain it differently. Working with AI feels surprisingly similar in that respect. The quality of the result depends a lot on whether I can recognise why the conversation is going in the wrong direction and give it the missing information.

That does not mean writing some enormous, carefully engineered prompt before every question. Most of the time it is much simpler. Show the actual code. Give the real error. Describe the expected behaviour. Say explicitly what should not change. If the answer solves a different problem from the one you have, tell it so.

Sometimes the first response is good. Sometimes the third or fourth one is the useful one.

For me, that back-and-forth is simply part of using the tool.

I prefer small changes that I can verify

The other habit that has made this workflow much more reliable is not asking for too much at once.

It is tempting to describe a large feature, get hundreds of lines of code back and paste everything into the project. The problem is that when something breaks, you now have a lot of new code to investigate and no clear idea which part caused the problem.

I prefer to move in smaller steps. Make one meaningful change, run the app and see what happened. If it works, continue. If it does not, solve that problem before adding more.

The same applies when I ask for changes to existing code. I often prefer instructions like “replace this function”, “add this variable here” or “pass this value to this component” over getting the entire file rewritten. It is easier to see what changed, easier to verify and less likely to accidentally destroy something that was already working.

I would recommend the same approach to anyone experimenting with AI-assisted coding for the first time. Give it one concrete problem, enough context to understand that problem, and then check the result immediately. Do not assume that a confident answer is automatically a correct one, but also do not discard the whole idea because one answer was bad.

If it misunderstands you, rephrase. If context is missing, add it. If the code fails, show the failure. If you dislike the approach, ask for another one.

Being able to communicate with the model is becoming a useful skill in itself.

Why I keep working this way

There are plenty of things about the current workflow that I hope will eventually become easier. Copying several files into a new conversation just so the model understands the project is tedious. Re-explaining the same architecture is tedious. Moving code manually between the browser and WebStorm is tedious. And having to remind the model about something I explained earlier can be frustrating.

Despite all that, it is already difficult for me to imagine voluntarily going back to doing everything without it.

I write less repetitive code, spend less time looking up syntax and can move through experiments much faster. More importantly, I can spend more of my time thinking about the application itself instead of every mechanical detail required to turn the idea into code.

I do not see ChatGPT as something that replaces a developer. At least not in the way I am using it now. It feels more like a very fast pair programmer with an enormous amount of knowledge, no access to my project unless I provide it, and a tendency to occasionally be very confidently wrong.

That sounds like a strange combination, but it is already extremely useful.

The biggest lesson for me so far is probably that getting good results is not about asking a question once and hoping for perfect code. It is about building enough context, testing what comes back and continuing the conversation until the proposed solution actually fits the problem.

When people say that AI gave them a useless answer, my first thought now is usually: did you ask it again?

Topics

ChatGPTcoding workflow