I'm a senior engineer building apps in public.
I document the engineering patterns that actually work.
Follow me if you want: π
βοΈ Better backend skills
π€ Smarter AI powered development
ποΈ Scalable architecture habits
π Guidance for busy developers who want to ship
Small interfaces are useful.
Until your codebase has 100 of them and nobody knows which one to use.
Interface segregation is not about making interfaces tiny.
It is about shaping them around consumer needs.
Start with the callers.
Group methods around stable roles.
Split when different consumers change for different reasons.
Name each interface for the job it serves.
And keep the right type easy to find.
If callers depend on methods they do not need, the interface is too broad.
If engineers need a treasure hunt to find the right interface, you split too far.
The useful middle is usually an interface sized around a clear role.
5 reasons duplicated config gets expensive as systems grow:
1. Every copy needs future edits
2. Environments multiply those copies
3. Drift can still pass deployment
4. Real exceptions become harder to spot
5. New developers inherit undocumented rules
Copying is easy, but tracking the copies is not.
Reliable concurrency isnβt about predicting timing.
Itβs about needing fewer timing predictions.
The more correctness depends on explicit signals, ownership, locks, and completion rules, the less you care which task happens to receive CPU first.
Deprecating old interfaces is not easy.
Some clients move slowly.
Some release quarterly.
Some have forgotten where the dependency even lives.
But, you still need go give them the date early.
You cannot control how fast every client moves.
You can control whether the deadline is a surprise.
5 signs your tests cost too much to maintain:
1. One new field breaks dozens of fixtures.
2. Constructor changes require mock rewiring everywhere.
3. Refactors break tests without changing behavior.
4. Developers copy setup instead of understanding it.
5. People avoid touching old tests.
Watch the edit patterns.
A service can be fast locally and still feel slow to users.
Cross-service delays accumulate across the call chain.
One 650 ms dependency can erase a lot of 70 ms wins.
Parallel calls still wait for the slowest dependency when every result is required.
Retries can turn one late call into several.
Give every hop a latency budget.
Set timeouts from the user deadline, not habit.
Watch p95 and p99, not averages alone.
Ask "What does this call have to wait for?"
State modeling changes queries.
Queries change how teams interpret data.
That interpretation changes reports, migrations, and incident diagnosis.
An ambiguous state does not stay inside one table.
Its ambiguity spreads everywhere and people will ask, "what state is this thing in?"
Before discussing the solution, answer 6 questions:
What is happening?
Who is affected?
What evidence supports it?
What constraints are real?
What must change?
What is outside scope?
Skip these and architecture starts with assumptions.
The most expensive 15-second review comment:
The one your team types 10 times.
"Add the rollback plan."
A few minutes adding that expectation to the PR template can prevent time wasted of repeated typing.
Small documentation habits compound.
Experience gives perspective. Programming is not dead, and never will be.
It's late September 2026. Sometime in September 1976, just after I entered college, I wrote and ran my first serious program. I have now been a programmer for literally 50 years.
And man, I have seen it *all*. Punched cards to glass ttys on minicomputers to workstations to the earliest micros. I've written business software, communication programs, development tools, games, language implementations, kernel device drivers, graphics libraries, a few critical pieces of infrastructure, and all manner of odd programs for which there is no taxonomy. Coded in a score of languages some of which are now forgotten. I've run over 70 open-source projects and contributed to at least as many more.
Along the way I noticed a few things, and wrote about them, and that made me famous. But I'm not here to write about that today. I'm here to write about what it means to be a programmer and a technologist with the Singularity upon us.
I have lost count of the number of times I have been told "programming is over". You kids may think this is a new claim that came into the world with LLMs. Hah! There's been that kind of talk at every step function in the quality of our programming tools. I first heard it when compilers were replacing hand-hacked assembler in the early 1980s, and I wouldn't be a bit surprised if somebody said it earlier when in-memory code replaced plugboards.
One constant amidst all this change is that the intentions in a user's head do not magically turn into executable instructions that a computer can run. Another constant is the mindset of the people who specialize in bridging the gap between wish and algorithm. A programmer is a programmer is a programmer - the tools are secondary.
"But it's different this time!" some people will say, "AIs are superhuman now!" So? What did you think the optimization stage of a compiler was, chopped liver? We've been relying on tools that exceeded human capabilities all along - we built them precisely because our meatbrains could not otherwise keep up with the demands of the job.
It's been a hell of a ride, that 50 years. I've succeeded and failed and succeeded again. Done a lot of work I'm proud of. Built at least a few things that look like they're going to last. None of that goes away because, hypothetically, an AI might have made me faster.
The work is never done, even if the tools can talk to us now. Because intentions still have to be communicated; people who specialize in that and develop right mindset to do it still have a major advantage over people who don't.
If you're feeling depressed because you spent years or decades developing hand-coding skills that are obsolete now? Buck up. You still have the most important asset - a mind that knows how to think that way.
I'm facing the winds of the Singularity with a smile. Because 50 years and I'm still standing - still shipping code that people can use, still learning, still refusing to be paralyzed by the pace of change.
The best therapy for your uncertainty? Get to work on something.
Some benefits of watching every deployment:
- Catch regressions sooner
- Connect failures to changes
- Make rollback calls with evidence
- Reduce how long bad code stays active
- Learn how your service behaves under real traffic
When latency rises but CPU does not, also check the following:
1.Connection wait time
https://t.co/6lHUt8C4Kz pool connections
3.Requests waiting for a connection
4.Acquisition timeouts
5.Retry volume
6.Pool size across every app instance
7.The downstream connection limit
The bottleneck may be outside the CPU chart.
Login gets the attention.
Revocation proves the architecture.
Can you kill one token?
One session?
One device?
One user?
How fast does every system stop trusting it?
Did you kill refresh paths too?
What about live connections?
Then test caches, regions, clock skew, and missed events.
A deleted cookie means almost nothing if trust is still alive.
A cache should reuse.
If writes erase the entry before enough readers can reuse it, the economics stop working.
So test caches with real write pressure.
6 signs your cache assumptions may fail under write pressure:
1. Hot keys change constantly
2. Invalidations spike during imports
3. Refills arrive in bursts
4. Several readers refill the same key
5. Hit rate drops after writes
6. Database load rises when the cache gets "busy"
5 compensating actions worth defining before shipping:
1.Refund a completed payment
2.Release reserved inventory
3.Cancel an external reservation
4.Retry a missing local write safely
5.Reconcile state when neither side can simply be reversed
Failure handling starts in design.
Your application checked the rule.
The tests checked the rule.
The API checked the rule.
And you still got invalid data.
Because there was one place that it didn't check.
The database.
If the rule must always hold, application validation alone is not enough.
Two modules can be tightly coupled without producing obvious failures.
Team A cannot ship without Team B.
Team B needs Team C.
Suddenly a technical dependency has become a calendar dependency.
The code still works.
The delivery process gets slower.