Everything Is UX
A while ago, MK was showing me some work he'd done on a client's deployment pipeline.
We'd been trying to make their deployments more traceable. At one point, Docker images were repeatedly tagged latest, which works fine until you need to answer a very simple question:
What code is actually running right now?
MK had reworked things so we could trace a deployment back to a specific Git commit. He showed me the result, including the commit SHA.
Something like:
5e2e0409f788e3ab70af561dbe7ad3892a57aa6c
I looked at it.
“Can you put SHA- in front of that?”
He looked at me. “Sure…?”
“Is that hard?”
“No.”
“Then put SHA- in front of it.”
At that particular moment, the label was completely unnecessary. MK was standing next to me, actively telling me that I was looking at a Git SHA.
But what about me six months later?
What about someone who hadn't built the deployment pipeline?
What about someone trying to figure out what's running in production at three in the morning, when something is broken and nobody is operating at peak cognitive capacity?
The raw SHA is data.
This:
SHA-5e2e0409f788e3ab70af561dbe7ad3892a57aa6c
gives me context.
Maybe I don't immediately know why I care about that SHA. But I have a foothold. I can infer something about what I'm looking at and start asking the next question.
Most importantly, the system already knew what that string was. Why make me figure it out again?
You are already designing experiences
When we talk about UX, it's easy to picture an application.
Buttons. Forms. Navigation. Someone in Figma deciding where things should go.
But an engineer reading a log is having a user experience.
So is someone running a deployment. Or reading an error message. Or calling your API. Or using your CLI. Or following your runbook. Or trying to figure out why CI is failing.
They're a person trying to accomplish something through an interface someone created.
That's UX.
And you don't get to opt out of designing it.
You can design that experience deliberately, or you can let implementation details, defaults, historical accidents, and accumulated workarounds design it for you.
Either way, somebody eventually has to use it.
“I keep meaning to fix that”
I ran into this again in that same client’s infrastructure repository.
MK had sensibly divided their OpenTofu configuration into separate modules. But then I watched him actually use it.
He was moving between directories to operate on different modules. Alternatively, he could provide paths directly, but then the commands got long and unwieldy.
“Doesn't this drive you absolutely crazy?”
Yes, actually. He kept meaning to do something about it. But there was always infrastructure work to do.
That's a trap I've seen repeatedly in engineering: we don't count improving the experience of doing the work as doing the work.
Nothing was technically broken. The infrastructure worked.
The person operating it just had to continually remember where things lived, navigate to the right place, construct the right commands, and maintain more of the state of the system in his own head.
So we stopped and worked on that instead.
We reorganized the repository. Large files became smaller ones with clearer responsibilities. Then we put a small interface in front of common operations with a Justfile.
Instead of remembering exactly how to invoke OpenTofu against a particular part of the system, we could do things like:
just apply <module>
just format <module>The infrastructure hadn't become more powerful. But operating it had become easier.
That counts.
Friction has a cost
It's easy to dismiss these things individually.
One cd isn't a problem.
Neither is looking up a path, mistyping a command, or stopping for three seconds to remember which AWS profile you're using.
Until you do it hundreds of times.
Friction costs time. It consumes attention. It creates frustration. And every unnecessary thing a person has to remember, infer, type, or check is another opportunity to get something wrong.
Sometimes we stop noticing that cost because we've gotten very good at working around it.
We search our shell history instead of remembering commands. We install fuzzy finders. We create aliases. We save snippets. We keep a terminal open because it's already in the right directory with the right environment configured.
Eventually, the workaround becomes so familiar that the underlying friction disappears.The system seems easy to use, but really, we're just extremely practiced at compensating for it.
But then we get a new laptop. Or someone new joins the team. Or we haven't touched the system in six months. Or production is broken.
That's when all of those little assumptions start sending us a bill.
Design for the person who doesn't have your context
There's an especially nasty problem with developer tooling: we often design systems at the exact moment when we're least qualified to judge whether they're understandable.
We're immersed in them.
Of course the deployment output makes sense while you're building the deployment pipeline.
Of course you know which directory to use when you've spent the last week building the infrastructure.
Of course you recognize the identifier. You named the variable.
But the person using your work tomorrow doesn't have all that context.
And neither will you, eventually.
Someone may encounter this system six months from now, at three in the morning, because they're on call and something is broken.
They may be tired. They may be under pressure. They may know the broader system extremely well but have no reason to remember the particular implementation detail you were thinking about when you built this.
Design for that person.
Make the system carry as much of its own context as reasonably possible.
Don't make the consumer reconstruct context you already have
The SHA- prefix didn't stay an isolated nicety.
Today, that client's deployment system carries the Git SHA throughout the path from build to production. Container images include it in their tags. The deployed application receives it as metadata. Structured logs include the environment, application version, and Git SHA. Deployment tooling can inspect what's currently running and determine which commit it came from.
What started as:
Please tell me what this string is.
became part of how the system answers:
What code is actually running?
There are many consumers along that path. Some are humans. Some are machines.
A machine doesn't need SHA- helpfully written in front of a value when it already has a structured field called gitSha. In fact, adding human-readable decoration might make its job worse.
A human encountering an image tag in a console benefits from the extra context.
Good UX doesn't mean making everything pretty for humans.
It means thinking about the consumer of an interface and giving them what they need in a form they can actually use.
Sometimes that's a descriptive label. Sometimes it's structured JSON. Sometimes it's a Justfile that turns a long, error-prone operation into a small, predictable command.
And sometimes it's designing away an opportunity for somebody to make a mistake in the first place.
What have you gotten really good at putting up with?
Once you start looking for this kind of friction, some of it is obvious. Maybe you have to look up the same command every time you need it, or keep information in your head that the system already knows. Maybe an operation is easy to get wrong, so you've developed a ritual of checking and rechecking before you run it.
Other friction is harder to see because you've already adapted to it.
You've memorized the command. You've made an alias. You know which output to ignore and which line actually matters. You have the snippet saved somewhere. You know that when the error says this, the real problem is usually that.
Those adaptations can be useful. There's nothing inherently wrong with a shell alias or a fuzzy finder. But expertise can make a poorly designed experience look less painful than it actually is, because experienced users become very good at building their own ways around the rough edges.
So when I look at a system now, I'm interested not only in whether someone can accomplish the task, but in what it costs them to do it.
Can we improve the experience's:
- Clarity: Is it obvious what I'm looking at and what I can do?
- Efficiency: How much work does it take to accomplish the thing I came here to do?
- Safety: How easy is it to make a mistake, especially one with consequences?
- Required context: How much do I need to already know, remember, or reconstruct before I can act?
Not every interface needs to optimize all four equally. Sometimes the friction is justified. Sometimes fixing it would cost far more than it's worth.
But first you have to notice it.
And some of the hardest friction to notice is the friction you've already adapted to.
What have you gotten really good at putting up with?
Because logs are interfaces. Deployment pipelines are interfaces. APIs are interfaces. CLIs are interfaces. Runbooks are interfaces. Repository structures are interfaces.
Every time someone encounters a technical system while trying to accomplish something, they have an experience of that system.
You can spend some time designing that experience.
Or you can leave them to design their own way around it.
Everything is UX, whether you choose to design it or not.