I had just learned about microservices.
They were amazing. Scalable. Independent. Deployable separately. The future of architecture.
So naturally, I rewrote my side project as microservices.
The project: A simple todo app. Maybe 100 users.
What I built:
- 7 separate services
- Docker containers for each
- Kubernetes for orchestration
- API gateway
- Service mesh
- Distributed logging
- Service discovery
Time to build: 3 weeks
Time to build as a monolith: 2 days
My manager saw it and laughed.
Manager: “Why did you use microservices for this?”
Me: “Because microservices are better than monoliths.”
Manager: “Better for what? At what scale? For what team size?”
Me: “…I don’t know.”
That was the problem.
I had learned microservices. But I hadn’t learned when to use them.
I’d learned the hammer. But I thought everything was a nail.
The Context Problem
Most learning focuses on the “what” and “how.”
What is Redux? How do you use it?
But rarely the “when.”
When should you use Redux vs. Context API vs. local state?
This is the skill gap that separates:
- Beginners: “I know how to use X.”
- Experts: “I know when to use X vs. Y vs. Z.”
Beginners have tools. Experts have judgment.
Story: The Two Database Designs
Two developers need to add a feature: user comments.
Developer A (knows tools, not context)
Thinking: “I know MongoDB. I’ll use MongoDB.”
Implementation: Comments in MongoDB.
Problem: The rest of the app uses PostgreSQL. Now there are two databases. The team doesn’t know MongoDB. Queries across databases are complex.
Result: Technical debt. Increased complexity.
Developer B (knows context)
Thinking:
- Constraint: “Team only knows PostgreSQL. Adding a new database has training cost.”
- Scale: “We’ll have maybe 1000 comments. Not big data.”
- Relationships: “Comments reference users and posts, both in PostgreSQL.”
- Tradeoff: “MongoDB might be slightly more flexible, but PostgreSQL is simpler for this use case.”
Decision: Use PostgreSQL.
Result: Consistent tech stack. Lower complexity. Team can maintain it.
The difference: Developer B asked “when should I use MongoDB?” not just “can I use MongoDB?”
The Contextual Decision Framework
For every tool or approach you learn, ask:
Question 1: What problem does this solve?
Not: “What can this do?”
But: “What specific problem is this designed for?”
Example: Redux
What can it do: Manage state in React apps.
What problem does it solve: Global state management in large apps with complex data flow.
When to use: When you have:
- Many components sharing state
- Complex update logic
- Need for time-travel debugging
- Large team needing predictable patterns
When NOT to use: When you have:
- Mostly local component state
- Simple data flow
- Small app
- Just you or small team
Question 2: What are the tradeoffs?
Every tool has costs and benefits.
Example: TypeScript
Benefits:
- Catch errors at compile time
- Better autocomplete
- Self-documenting code
- Easier refactoring
Costs:
- Learning curve
- More code to write
- Build step required
- Can slow down prototyping
The decision depends on context:
Large team, long-term project? TypeScript wins.
Solo dev, weekend hack? Maybe skip it.
Question 3: What are the constraints?
Real-world constraints determine what’s actually viable:
Common constraints:
- Time: “We need this shipped tomorrow.”
- Team knowledge: “Nobody on the team knows X.”
- Budget: “We can’t afford enterprise tools.”
- Scale: “We have 10 users, not 10 million.”
- Existing system: “We already use Y, and it’s working.”
Example:
You want to use: Kubernetes
Constraints:
- Team: 2 developers, neither knows Kubernetes
- Scale: 50 users
- Budget: $0 for infrastructure
- Timeline: 2 weeks to launch
The right choice: Heroku or similar PaaS. Not Kubernetes.
Why: Kubernetes is overkill. The constraints make it a bad fit.
Question 4: What’s the future state?
Think beyond the immediate need.
Ask:
- Will this scale with growth?
- Will this be easy to maintain?
- Will this constraint future decisions?
Example:
Immediate need: Store 100 records.
Bad choice: CSV file (works now, breaks at scale).
Better choice: SQLite (works now, scales to thousands).
Good choice: PostgreSQL (works now, scales to millions).
The right choice depends on expected growth.
The Skill Selection Matrix
Here’s how experts choose:
The key step most people skip: “Document why.”
If you can’t articulate why you chose X over Y, you don’t understand the context.
Real-World Examples
Example 1: State Management
Problem: Manage state in a React app.
Options: Local state, Context API, Redux, Zustand, Jotai, Recoil.
The decision tree:
1. Is the state local to one component?
→ Yes: Use useState. Done.
2. Is it shared between a few nearby components?
→ Yes: Lift state up or use Context API. Done.
3. Is it global state needed everywhere?
→ Yes: Continue…
4. Is the update logic complex?
→ Yes: Use Redux (predictable updates).
→ No: Use Zustand or Context (simpler).
5. Do you need time-travel debugging?
→ Yes: Redux DevTools.
→ No: Simpler solution.
6. What does your team know?
→ They know Redux: Use Redux.
→ They don’t: Use simpler alternative.
The point: The “best” tool depends on 6+ factors, not one.
Example 2: Testing Strategy
Problem: Test a web application.
Options: Unit tests, integration tests, E2E tests, manual testing.
The decision:
Context 1: Startup, 2 devs, MVP
- 80% manual testing
- 20% critical path E2E tests
- 0% unit tests initially
Why: Speed matters most. Comprehensive testing slows iteration.
Context 2: Enterprise, 50 devs, production app
- 70% unit tests
- 20% integration tests
- 10% E2E tests
- Minimal manual testing
Why: Reliability matters most. Many devs need safety nets.
Same problem. Different context. Different solution.
Example 3: Code Organization
Problem: Structure a JavaScript project.
Options: One file, multiple files, modules, folders by feature, folders by type.
The decision:
Project size: 100 lines
→ One file. Don’t over-engineer.
Project size: 1,000 lines
→ Multiple files by purpose (api.js, utils.js, components.js).
Project size: 10,000 lines
→ Folder structure by feature. Clear modules.
Project size: 100,000 lines
→ Monorepo with packages. Enforce boundaries.
The context (project size) determines the structure.
Building Contextual Judgment
How do experts develop this judgment?
Tactic 1: Learn the Failure Modes
For every tool, learn when it fails.
Example: Microservices
When they work:
- Large organization
- Independent teams
- Different scaling needs per service
- High throughput requirements
When they fail:
- Small team (coordination overhead too high)
- Low traffic (complexity not worth it)
- Tight coupling between services (lost the benefits)
Learning the failure modes teaches you the boundaries.
Tactic 2: Study Post-Mortems
Read about when things went wrong.
Example post-mortem patterns:
“We chose X, but we should have chosen Y because…”
- “We chose microservices for a small team. Coordination cost killed us.”
- “We chose MongoDB for relational data. Query complexity exploded.”
- “We chose to build our own auth. Security vulnerabilities were inevitable.”
These teach you the context where each tool fails.
Tactic 3: The “Why Now?” Question
Every time you see a tool recommended, ask:
“Why is this being recommended now? What changed?”
Example:
2015: “Use jQuery.”
2025: “Don’t use jQuery.”
What changed: Modern browsers have better APIs. Frameworks handle DOM manipulation better.
Lesson: jQuery was right for 2010 context. Wrong for 2025 context.
Understanding what changed teaches you what context matters.
Tactic 4: Compare and Contrast
Don’t just learn tools. Learn how they compare.
Example comparison: REST vs. GraphQL
REST is better when:
- Simple CRUD operations
- Caching is critical
- Team knows REST well
- Public API (widely understood)
GraphQL is better when:
- Complex data requirements
- Mobile apps (minimize requests)
- Rapid UI iteration
- Internal API (you control clients)
This comparison teaches you the decision criteria.
Tactic 5: Track Your Decisions
Keep a decision log.
Every significant technical decision:
- What did you choose?
- What were the alternatives?
- Why did you choose this?
- What was the context?
Review it quarterly.
Ask: “Was this the right decision? What would I do differently?”
Example from my log:
Date: 2023-05-12
Decision: Use PostgreSQL instead of MongoDB for user data.
Alternatives: MongoDB, MySQL
Why:
- Relational data (users, posts, comments all related)
- Need ACID transactions
- Team knows SQL
- Scale is manageable (<1M rows)
6 months later: Still the right call. Relational queries are common. Transactions were crucial.
Learning: Trust relational databases for relational data.
The Anti-Patterns
Anti-Pattern 1: Resume-Driven Development
The trap: Choose tools to pad your resume, not to solve the problem.
Example:
“I’ll use Kubernetes for this project. Not because we need it, but because I want Kubernetes on my resume.”
Result: Over-engineered, hard to maintain.
The fix: Choose tools for the problem, not for your career.
Anti-Pattern 2: Hype-Driven Development
The trap: Use the latest hot technology regardless of fit.
Example:
“Everyone’s talking about Deno. Let’s rewrite everything in Deno!”
Result: Chase every trend, never build expertise.
The fix: Let trends mature. Adopt when there’s proven value.
Anti-Pattern 3: The Golden Hammer
The trap: “I know X, so I’ll use X for everything.”
Example:
“I’m a React expert, so I’ll build the backend in React too… wait, that doesn’t work.”
Result: Force tools into contexts where they don’t fit.
The fix: Learn multiple tools and when to use each.
Anti-Pattern 4: Premature Optimization
The trap: Solve for hypothetical scale you’ll never reach.
Example:
“This app might have 1 million users someday. Let’s architect for that now.”
Current users: 10.
Result: Complex system for a simple problem.
The fix: Solve for current scale. Refactor when you have actual problems.
Anti-Pattern 5: Not Invented Here Syndrome
The trap: Build everything yourself instead of using existing solutions.
Example:
“I’ll build my own authentication system. How hard can it be?”
Result: Security vulnerabilities, wasted time.
The fix: Use established solutions for solved problems.
The Decision Template
Here’s a template I use:
Problem: [What are you trying to solve?]
Constraints:
- Time: [How long do you have?]
- Team: [Who’s building this? What do they know?]
- Scale: [How many users/requests/records?]
- Budget: [What can you spend?]
Options:
Decision: [What you chose]
Why: [Your reasoning]
Trade-offs: [What you’re giving up]
Example:
Problem: Add real-time chat to app.
Constraints:
- Time: 2 weeks
- Team: 2 devs, know React and Node
- Scale: 500 concurrent users
- Budget: $100/month
Options:
- Build with WebSockets: Full control, but time-consuming.
- Use Firebase: Fast, but vendor lock-in.
- Use Socket.io: Middle ground, open source.
Decision: Use Socket.io
Why:
- Team can learn it in 2 days
- Scales to our needs
- Open source (no lock-in)
- Fits timeline and budget
Trade-offs:
- Not as feature-rich as Firebase
- More setup than Firebase
- Less control than raw WebSockets
This process forces clear thinking.
Developing Intuition
Eventually, you don’t run through this checklist every time.
Experts develop intuition.
But intuition is compressed experience.
How to build it:
1. Make Many Decisions
You can’t develop judgment without making decisions.
Even wrong decisions teach you.
My first 10 architectural decisions were mostly wrong.
But each one taught me what to avoid next time.
2. Get Feedback
Did your decision work out?
Track outcomes:
- Did the project succeed?
- Was it maintainable?
- Did it scale?
- Would you make the same decision again?
This feedback loop calibrates your intuition.
3. Study Experts’ Decisions
Read engineering blogs from successful companies.
Look for:
- What problem they faced
- What they chose
- Why they chose it
- What happened
Examples:
- Netflix tech blog
- Stripe engineering blog
- Airbnb engineering blog
You’re learning their context-to-decision mappings.
4. Simulate Decisions
When you read about a technical problem, pause.
Ask yourself: “What would I choose?”
Then read what they chose and why.
Compare your reasoning to theirs.
This builds pattern recognition without having to live through every scenario.
Final Thoughts
I used to think expertise was knowing all the tools.
React. TypeScript. GraphQL. Docker. Kubernetes. PostgreSQL. Redis.
But that’s not expertise.
Expertise is knowing:
- When to use React vs. Vue vs. Svelte
- When to use TypeScript vs. JavaScript
- When to use GraphQL vs. REST
- When to use Docker vs. not
- When to use Kubernetes vs. simpler solutions
- When to use PostgreSQL vs. MongoDB vs. Redis
The difference is judgment.
Beginners collect tools.
Experts develop judgment about when to use each tool.
And that judgment comes from:
- Understanding the problem each tool solves
- Knowing the tradeoffs
- Considering the constraints
- Learning from failures
- Building pattern recognition
So don’t just learn how to use tools.
Learn when to use them.
That’s the skill that separates good developers from great ones.
What’s a tool you know how to use but aren’t sure when to use? How could you learn its boundaries?