Writing
What Painters Can Teach Us About AI
The useful question is not whether AI can write code. It is which parts of the craft become more valuable when it can.
Every few weeks another layoff announcement arrives, followed by a wave of posts explaining that software jobs are over. The reactions usually split into two camps: AI will replace us all, or AI is a glorified autocomplete and nothing will change.
Neither answer is very useful.
A better question is narrower: when a machine can produce a plausible first draft of almost anything, which parts of software work become more valuable? The history of painting offers one way to think about it.
What the camera actually changed
Before photography, if you wanted an accurate picture of a person or a place, you hired a painter. The camera made accurate reproduction cheap and fast. Painters who competed on realism were in trouble.
But painting did not disappear. It moved toward things the camera was bad at: interpretation, atmosphere, abstraction, and point of view. The job changed from reproducing what a scene looked like to deciding what the scene meant.
That is not a perfect analogy for software. A painting is not a payroll system, and a camera is not a language model. Still, the pattern is familiar.
When the cost of producing a standard artifact falls, the value shifts to the judgment around the artifact.
What AI is genuinely good at
AI is already useful for work that is easy to specify and easy to check. In front-end development, that includes:
- Turning a clear data shape into a table or form.
- Writing the first version of a unit test.
- Explaining an unfamiliar error message.
- Translating a design token into CSS.
- Generating boilerplate for a new route or component.
Those tasks are not trivial. They take time, and saving time is real value. But notice the shape of the work: the input can be described, and the output can be checked.
What it is bad at
The failures are less about syntax and more about context. AI does not know which decision matters, who will maintain the code next, or what the team already tried last quarter.
Here is a small example. Ask for a search effect in React and you might get something like this:
useEffect(() => {
fetch(`/api/jobs?q=${query}`)
.then((response) => response.json())
.then(setJobs);
}, []);
It looks reasonable. It is also wrong in several ordinary ways. The effect never reruns when query changes. It does not handle a failed request. It does not cancel the previous request, so a slow response can overwrite a newer one. And it assumes every response is safe to render.
A production version has to make those decisions explicitly:
useEffect(() => {
const controller = new AbortController();
async function loadJobs() {
setStatus('loading');
try {
const response = await fetch(`/api/jobs?q=${encodeURIComponent(query)}`, {
signal: controller.signal,
});
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
const data: Job[] = await response.json();
setJobs(data);
setStatus('success');
} catch (error) {
if (error instanceof DOMException && error.name === 'AbortError') return;
setStatus('error');
}
}
loadJobs();
return () => controller.abort();
}, [query]);
The second version is not impressive because it is complicated. It is useful because it answers questions the first version ignored: what happens while loading, what happens on failure, and what happens when the user types again before the first request finishes.
An AI can write either version. The difference is whether someone owns the questions.
The skills that move up the stack
When drafts are cheap, review becomes expensive. I think that shifts value toward a few skills.
Specification. Being able to describe the behavior precisely enough that another person, or a model, can implement it. That means defining empty states, error states, edge cases, and what “done” means.
Reading code. Not just writing it. If you are going to accept generated code, you need to be able to spot the missing dependency, the unsafe cast, or the request that never gets cancelled.
Debugging. AI is often good at suggesting causes. It is not good at reproducing the exact conditions in your app. The person who can isolate the problem still has the leverage.
Taste. Knowing that a feature is technically correct but still feels wrong to use. That is hard to outsource because it depends on judgment built from real interfaces and real users.
Accountability. Someone has to decide that the tradeoff is acceptable, document it, and fix it when it breaks. That responsibility does not disappear because a model helped write the code.
A practical way to respond
The least useful response is to pretend nothing is changing. The second least useful is to try to beat a model at typing speed.
A better approach is to use AI for the parts it is good at and deliberately practice the parts it is not. When I use a generated solution, I try to do three things:
- Rewrite the prompt with the actual constraints, not just the happy path.
- Review the output as if a contractor submitted it without context.
- Write down why the final version differs from the first draft.
That last step matters. If I cannot explain the change, I probably do not understand the code well enough to ship it.
What this means for my own work
I have spent years learning to code and studying computer science. I do not think that was wasted, but I do think the job is moving away from “can you produce the code” toward “can you decide what should exist and prove that it works.”
The painters who adapted did not stop caring about craft. They changed what the craft was for. Software is likely to do something similar. The people who do well will not be the ones who refuse the new tools or the ones who accept every output. They will be the ones who can tell the difference between a plausible answer and a correct one.