The Questions I Ask Before Writing a Single Line of Code

· 6 min read

The Questions I Ask Before Writing a Single Line of Code

Early in my career, I measured productivity by lines of code written. I'd get a task, open the IDE, and start typing. If I was writing code, I was making progress. Right?

Wrong. Some of the biggest time sinks in my career came from starting too fast. Building the wrong thing. Solving a problem that didn't need solving. Optimizing something that didn't matter. All because I skipped the thinking phase and went straight to the doing phase.

Now I have a set of questions I run through before I write any code. It takes 15 to 30 minutes. It saves days.

The Questions

1. What problem am I actually solving?

This sounds obvious. It isn't. The ticket says "add caching to the API layer." But why? Is it because response times are slow? Is it because the backend can't handle the load? Is it because the app feels sluggish on slow networks?

Each of those problems has a different solution. Caching might be the right answer for one and the wrong answer for another.

Ticket: "Add caching to the feed API"

Possible actual problems:
- Feed load time is >3s on cold start → Caching helps
- Backend is hitting rate limits → Caching helps
- Feed flickers during navigation → Not a caching problem, it's a state management problem
- Feed shows stale data after posting → Caching makes this WORSE

I always restate the problem in my own words and verify it with whoever filed the ticket. Half the time, the real problem is different from what was written down.

2. Who is affected, and how much?

Not all problems are worth solving. A bug that affects 0.01% of users on a single device model is different from a bug that affects 5% of all sessions.

Before writing code, I want to know:

  • How many users does this affect?
  • What's the severity? (Crash? Degraded experience? Cosmetic?)
  • Is it getting worse or stable?

This determines how much time the fix deserves. A 5-minute patch for a rare edge case is fine. A week-long refactor for the same issue is not.

3. What's the simplest thing that could work?

My natural instinct is to build the "right" solution. The scalable one. The one with proper abstractions and extensibility points.

I've learned to resist that instinct until I've first considered: what's the simplest possible fix?

// The "right" solution: generic caching framework
interface CacheStrategy<K, V> {
    fun get(key: K): V?
    fun put(key: K, value: V)
    fun evict(key: K)
    fun invalidateAll()
}
 
class LruCacheStrategy<K, V>(maxSize: Int) : CacheStrategy<K, V> { ... }
class TtlCacheStrategy<K, V>(ttlMs: Long) : CacheStrategy<K, V> { ... }
class CompositeCacheStrategy<K, V>(...) : CacheStrategy<K, V> { ... }
 
// The simple solution: just cache the one thing that needs caching
private var cachedFeed: FeedResponse? = null
private var cacheTimestamp: Long = 0
 
fun getFeed(): FeedResponse {
    if (cachedFeed != null && System.currentTimeMillis() - cacheTimestamp < CACHE_TTL) {
        return cachedFeed!!
    }
    return fetchFeed().also {
        cachedFeed = it
        cacheTimestamp = System.currentTimeMillis()
    }
}

Sometimes the simple solution is the right solution. Sometimes it isn't. But you should always know what the simple option is before choosing the complex one.

4. What are the failure modes?

This is the question I spend the most time on. Before building something, I think through how it can break.

  • What happens if the network is down?
  • What happens if the response is malformed?
  • What happens if this runs on a device with 2GB of RAM?
  • What happens if the user does this action twice in rapid succession?
  • What happens if a dependency is slow or unavailable?

For each failure mode, I decide: should I handle it, or is it acceptable to fail? Not every edge case needs handling. But I want that to be a conscious decision, not a surprise in production.

5. How will I know if this works?

Before writing the code, I define what success looks like. Concretely.

  • "Feed load time drops below 1.5 seconds at p50" (measurable)
  • "No more crash reports from this code path" (verifiable)
  • "Users see updated content within 30 seconds of posting" (testable)

If I can't define success in measurable terms, I probably don't understand the problem well enough yet.

6. What will I NOT do?

Scope creep is the silent killer of engineering velocity. Every task has adjacent improvements that are tempting to bundle in. "While I'm in this file, I might as well refactor this other thing."

I explicitly list what's out of scope. I write it down. This protects me from my own instincts and gives me something to point to when someone asks "can you also..."

7. Is there existing code or patterns I should follow?

Before building something new, I search the codebase for similar patterns. Most codebases have conventions for how things are done, even if they're not documented.

If there's already a pattern for API caching, I follow it. Even if I think my approach is better. Consistency across the codebase is worth more than local optimality.

8. What's the rollback plan?

If this change goes wrong in production, how do I undo it? Is it behind a feature flag? Can I revert the commit? Does rolling back require a data migration?

The answer to this question often changes how I build the feature. If rollback is hard, I build more carefully. If rollback is easy (feature flag, config change), I can be more aggressive.

Why This Works

These questions take 15 to 30 minutes. The code I write after asking them takes significantly less time, because I'm not wandering. I know what I'm building, why I'm building it, how it can fail, and how I'll measure success.

The questions also create artifacts. When someone asks "why did you build it this way?" I have answers ready. Not from memory, but from the notes I took before I started.

The best engineers I've worked with all share this trait: they think more than they type. The questions might be different, but the discipline is the same. Understand before you build. It's the highest-leverage habit in software engineering.

Related Posts