When you picture a programmer, you probably imagine someone hunched over a keyboard, lines of cryptic text flying across the screen. While writing code is a key part of the job, it’s just the final, visible step in a much deeper process. The real essence of programming isn't typing; it’s problem-solving.
Think about it: every app on your phone, every website you visit, was built to solve a problem. Connecting people, automating a tedious task, or analyzing complex data—these are all solutions crafted by programmers. Code is merely the language we use to explain our solution to a computer. The real work, the part that requires creativity and critical thinking, happens long before our fingers hit the keys.
It All Starts with the "Why"
A skilled developer doesn't start by asking, "What code should I write?" They start by asking, "What problem are we trying to solve?" This is where methodologies like spec-driven development come into play. Before writing a single line of code, the goal is to create a precise, unambiguous "spec" or contract. This document clearly defines what the software must do, the conditions it will operate under, and the expected outcomes.
This phase is about dissecting a messy, real-world issue into a clear, defined challenge. It involves asking critical questions, listening to stakeholders, and turning a vague idea into a logical roadmap. You can't build a sturdy house without a blueprint, and you can't write effective software without a solid spec.
Designing the Blueprint
Once the problem is clearly defined, the programmer puts on their architect's hat. This is a highly creative and logical phase where they brainstorm approaches, compare different algorithms, and design the software's structure. They weigh trade-offs: Should we prioritize speed or memory usage? How can we build this so it’s easy to update six months from now? They are designing a system, considering how data will flow, how components will interact, and how to create a solution that is not just functional but also efficient and maintainable.
A Lesson from a Legacy Authentication System
The translation of this blueprint into code is where modern tools like Large Language Models (LLMs) seem like magic. But they can't solve problems that stem from a system's hidden history and evolution.
I learned this firsthand at a company called HERP. We had a rudimentary data API that was originally developed for internal usage and expanded as and when needed. The game changed when our tenants showed interest in operating on that data from our product to do stuff in their own applications. Suddenly, an internal tool needed to become a secure, customer-facing product.
This new business requirement exposed a fundamental architectural flaw that wasn't obvious. The real challenge became clear: we needed to be able to provide access to the tenants and handle permission scopes, but the API was never designed for this. An LLM, fed this problem, would suggest standard, textbook solutions. But it would be ignorant of the fact that access was hardcoded and the concept of granular, customer-level permissions didn't even exist in the codebase.
The solution required a multi-layered, human-centric approach.
- Investigation: First, we had to perform digital archaeology💀 to understand the API's existing, brittle structure.
- User Research: Next, we had to talk with our internal developers (the API's first users) to understand their needs and define what the DX would look like for a new system.
- Architectural Design: Armed with this technical and user knowledge, the true solution emerged. We needed to expand the in-house auth gateway service, implementing a proper client credentials flow.
This project wasn't just about adding a feature. It was about strategically evolving an internal tool into a scalable, secure, and user-friendly product. No LLM could have navigated that complex journey. It required investigation, user empathy, and forward-looking architectural design. That's the core of problem-solving. 💡
You Are the Problem Solver
That experience highlights the true role of a programmer when using AI tools. The LLM can handle some of the typing, but the thinking remains our job. Your role is to:
- Evaluate the Generated Code: Does this align with the architecture? Is it secure? Does it respect the hidden constraints of our system?
- Integrate and Adapt: Code snippets are not plug-and-play. They must be carefully integrated and often heavily modified to fit into the larger project.
- Test and Debug: LLM-generated code can be buggy or inefficient. Rigorous testing and debugging (a core problem solving skill!)are non-negotiable.
- Optimize and Refine: You are responsible for ensuring the final product is performant, scalable, and maintainable long after the initial code is written.
Leaning on programming languages or code-generation tools alone is like learning grammar without learning how to tell a compelling story. The tools are essential, but they are in service of your intellect.
Ultimately, programmers are architects of logic. The code we write, whether by hand or with AI assistance, is simply the manifestation of our ability to solve a problem. The tools will always change, but the fundamental challenge of turning a complex human need into an elegant, working solution remains the very heart of the craft.