You know what's hilarious? Fresh bootcamp grads write code that's too simple. Six months later, after discovering design patterns, they write code ...
For further actions, you may consider blocking this person and/or reporting abuse
Adam - The Developer on November 23, 2025
For further actions, you may consider blocking this person and/or reporting abuse
I could build Netflix I just don't wanna
Ohh, we all know you've already built one, Ben 😆
It's probably hidden in a private Github repo somewhere.
Really solid post. I’ve learned this lesson the hard way: if I’m building something and I keep running into roadblocks or the design keeps getting more complicated than it should be, that’s usually my signal to step back. Nine times out of ten the problem isn’t the code — it’s that I’m over-engineering the entire thing.
Simplifying the approach almost always gets me unstuck.
I’m not building Netflix, I’m building something that needs to work, be maintainable, and survive reality. This post put that mindset into words perfectly.
Thanks and yeah, most of the “complexity” we run into is self-inflicted. When the design starts fighting you, it’s usually because you’re solving problems you don’t even have. Strip the extra layers and the whole thing suddenly makes sense again.
Actually, I'm building Netflix 😆
But I agree with your posts
no wayyyyy, i'd never thought an actual engineer from Netflix would see this blog but this is the best validation of my post yet! 😆
Interesting take, but it only reveals one big truth: Unless you know all this, you don't know you're overdoing things.
It is an unescapable situation: When you're a junior, you desire knowledge and practice. Acquiring said goals turn you into a senior capable of backtracking your steps and realizing this. In other words, this is inevitable.
Unless you have your own almighty senior dev carrying you all your life telling you when to and when not to, you'll need to:
If nobody holds your hand on this, you need to go through the process yourself.
So while your article is insightful, it won't stop people from doing all of it, and that's OK.
Couldn’t agree more. Every dev, including me, went through the over-engineering phase. this article’s more like a signpost along the road, not a shortcut. Thanks for the perspective!
You're being flippant and over-generalizing. And frankly, your article has the tone of just the type of person who would write code like this, or perhaps the tone of someone who only recently realized he doesn't always have to.
The truth is there is a time and place for designing for the future. That's what architecture is by definition. Only with experience, though, do you learn how to discern what is likely to change from what is truly unknown.
You're absolutely right that I recently learned this lesson – that's exactly the point. I spent years over-engineering and recently realized I didn't have to. This article is me processing that realization.
And yes, there's absolutely a time and place for designing for the future. The entire "When Abstraction Actually Makes Sense" section covers exactly that. The issue isn't abstraction itself but rather the premature abstraction based on hypothetical futures instead of real requirements.
The flippant tone is intentional. After maintaining codebases with 11-file preference systems, I think a little irreverence is warranted. If the style isn't for you, that's fair. But the underlying message: wait for actual requirements before abstracting is pretty standard advice (Rule of Three, YAGNI, KISS, ...etc).
Well, I disagree that it's strictly based on waiting for actual requirements. It's based on how you anticipate the requirements to change, which can come from direct past experience, industry best practices, logical intuition, and general professional foresight. A very simple example is the common architectural choice to separate the presentation and business layers--even before there is no official requirement to build for more than one client.
Classic! Funny thing is, when I was a young dev, I thought something was wrong with me for not grasping the “genius” of ten layers of abstraction 😄
same haha, I used think something is wrong with my code if it doesn't involve me writing a class and something corporate-ish
A lot of apple pie here, sure not to draw any disagreement. The experienced engineer will find balance — forethought and experience go a long way towards a durable codebase that can grow instead of needing to be rewritten (all too common) and it takes very little extra effort.
I think there is an in-between. Don't waste time abstracting or implementing but write your code in a clean way where an interface can be thrown in, in the future keep public interfaces generic so you can replace them. You don't waste time or add any bloat and when the time comes you just throw up an interface and abstract later. Designing for the future is more about not shooting yourself in the foot and thinking about the long term then actually implementing it.
Amen brother! Thanks for writing this. This should be required reading for all junior Devs.
See it so often with juniors (yes, I did it) that they think they are proving their creds by using the most advanced language features/patterns they can think to apply to the problem.
No! Only use that sh1t if it really makes the solution simpler overall!
Always remember the next Dev is quite likely to be less smart/experienced than you - If you are a beginner on the project, chances are they will put even a MORE fresh beginner on the project in the future. Be kind to your future colleagues. Don't add unnecessary complexity.
Simplicity always delivers significant benefits in my experience.
But 'copy-paste' 10 times may not be simple - not if there is a better approach that encapsulates the similarities - and is simpler overall.
Thanks for this post again. I really loved it. Spot on.
No offence, but...
Anything more complicated than that would be silly, and the options object leaves the door open for adding special cases without breaking code.
If you ever outgrow a function like that, you're probably at the stage where that full-size abstraction becomes reasonable anyway.
I'd go further: If you see duplicated code, consider:
Sometimes it's better to just leave code duplicated, drop a comment, and invest the effort of refactoring it if it changes.
For one, first time the logic changes, you actually see whether it changes at all sites of the duplicated logic or just one. If only one changes, that's a hint that the similarity is only a coincidence anyway. This also confirms that the logic changing is a thing that happens. It makes it more likely that it'll change again, so having a central place to change it rather than applying any change twice is worth the effort.
As for "I might need this in the future", I have more mixed feelings. People who have been coding for a while might just have a feeling for these things. In fact, I'm living this right now: working on broadening a feature for a new requirement that I saw coming the day the feature was first implemented.
When I implemented that first feature, I definitely had it in the back of my head to not shoot future me in the foot by doing things that would get in the way of refactoring and now present me is quite thankful for that.
Had there been more potential for an abstraction there, I would have done it from the start, and it would have been the right call. But the important point here is, this wasn't a vague "this could technically be needed", but a very specific "this client will totally ask us to support this exact case in a year or two" (it ended up only being 2 months). Intuition based on experience is key here.
I thought this post will be about JAVA, the fu** TS does here??? It is not like Netflix prefer NestJs over Spring Boot
OMG, Just why you validate in service??? Never heard about Validation Pipes with Class Validator???
Abstract classes like
BaseServicemay not seem appealing at first, but in a large application they save a lot of time by centralizing common logic such asfindOneById. You avoid duplicating the same methods across multiple services, and if a specific service needs different behavior, you can simply override the base implementation.My post isn’t about Java vs TS but it’s more about over-engineering patterns that appear across all languages. I used Ts/Nest because that’s where I see it the most in my day-to-day, but the same issues show up in Spring ( a 2nd topic i've always liked to write about ) and everything else too.
As for validation: Pipes + class-validator are great, but they don’t replace domain-level validation. Request validation ≠ business validation.
Regarding base services: they help in monolithic patterns, but in many cases they introduce tighter coupling and reduce clarity. Whether they’re worth it depends heavily on the architecture and team.
Regarding base services: that is usually moved to common-lib package to be shared across microses
My rule for creating an abstraction is always: Are these 2 things intrinsically the same or coincidentally the same? Often there is not enough info to answer it, in which case two copies with a comment is a better start.
Insightful and entertaining. I have been 2 decades in the field and still learn new wisdom here. Please write follow up article with topic like dependency injection and options pattern as I think a lot of overkill code in this area.
Oh yesss, I’m actually planning a blog on DI in NestJS right now. It’s wild how many implementations are over-engineered, unnecessary, or just… circular. And circular dependencies in NestJS are still one of the most common headaches out there.
That's great, looking forward to that. And thanks a lot for the article!
These are things I always wanted to write about, but I always stopped myself because it was too much work to put them all together in an article (plus, that wouldn't offend anyone 😁). And now that AI writes code for us, there's no longer the problem of copy-paste/refactor... So today, this article is even more relevant! Thanks
Absolutely spot on, Adam! I’ve seen so many codebases where developers over-abstract everything “just in case,” and it becomes a nightmare to maintain. Your points about premature abstraction and the “future-proofing” fallacy really resonate simple, readable code often scales better than convoluted patterns nobody really needs. I especially liked the part about external dependencies versus stable logic keep what’s stable simple, abstract what actually changes. This is such a practical approach that saves time and sanity. Thanks for calling out these anti-patterns in such a detailed and entertaining way. Definitely something every developer should read before adding another layer of unnecessary abstraction.
This post really resonates! The idea of premature abstraction is so relatable it's easy to over-engineer when we think we’re future-proofing, but it often just adds complexity. Keeping things simple and focusing on solving real problems is key. The "interface with one implementation" is hilarious but true. Sometimes, the most scalable code is the simplest, most straightforward one. Great reminder to avoid overthinking and just write clear, maintainable code! #KeepItSimple #TechDebt #CodeQuality
Love the insights on overengineering code.
It’s so easy to fall into the trap of over-abstracting without considering the real needs of the project. Sometimes, the simplest solution is the best. Definitely something every developer should keep in mind when deciding what to abstract and what to leave as is.
Yes I agreee! However most CS courses teach heavily on these abstractions (because they needed to be taught). But how and when to use them depends on one’s judgment.
Thanks for touching on such an important topic.
This is right on the money! KISS, YAGNI, and "the best part is no part" are my favorite principles, having been in the industry for over 30 years. "Complex systems" do not work, where "complex" means "impossible to maintain."
Adam, I am going to ask every new hire to learn this article by heart, along with code samples (I might translate it to Java or C# for them).
That means so much to me, especially coming from someone with 30 years in the industry!
I actually wrote another same post but in Java but that was only for my own team haha. This TS version was actually written much later.
In summary, it seems like all you've said is "don't use interfaces"
I have a great reason to use interfaces:
Clearly define API contracts, how services are intended to be used. Avoids spaghetti down the line in a modular monolith.
No interfaces is like saying all 3rd party API you integrate with should just let you import and modify their source code directly.... No, they provide a web API: here is what our services can do, and how you should use them. Same with an interface API, sitting on the boundary of each well designed module. Does anyone external module need to use my services? No? Okay then no interface needed.
I think there's a misunderstanding here. I'm not using interfaces as a typing reference, I'm using them as boundaries.
Interfaces in a modular monolith act like contracts: They explicitly define what a module exposes, and they prevent accidental reach-throughs or spaghetti coupling as the codebase grows.
This isn't " dont use interfaces ", it's " use interfaces at the boundary where contracts matter. "
Write it, write it again, oh!! A pattern, implement it. That's right.
One thing I noticed, TypeScript looks like a mess. Or I wondered too much away from it. I didn't write it for a year now (just basic stuff).
As someone who personally has walked (hopefully) trough the mindset evolution described here -
Novices: Copy-paste everything
Intermediates: Abstract everything
Experts: Know when to do neither
I would agree with almost everything said here in. In fact, in my late projects, where abstraction is required by design, development is more easier if I just go the simplest way first and write the "damn service" as I see fit. Then adapt it to fit in the architecture abstractions - this saves a lot of time and keeps the context of code changes smaller.
Fantastic article, probably the best I've ever read on DEV! +💯
This should be taught to those who teach OOP, 'cause that's the source of all evil.
Generalizing the "evil" into OOP devs is just wrong.
this was against OOP teachers. They should be the first to educate against pattern abuse.
"The goal is solving problems with the minimum viable complexity."
Yep...
yeah exactly, that's one of those patterns that sounds 'enterprise' but doesn't actually buy you anything unless you know you'll have multiple implementations.
I feel much of this ought to be codified into a static analysis tool that people should pass cleanly before submitting a pull request.
Or...
Or...
This is the most corporate way of saying "this code is garbage" I've read in quite a while 🤭
Great article!
I see three guiding principles here: KISS, YAGNI and WET :)
If I remember correctly, WET is the opposite of the DRY principle. WET is not a principle. WET should not exist. Sometimes, it is OK to repeat yourself, but that is just an exception, and exceptions to an excellent principle like DRY cannot be principles by themselves. If we accept one as a principle, by definition, its opposite is an anti-principle. I can meet you mid-way and call the anti-principle an exception, but that's it.
Great article. "Every piece of code we write is a liability.. not asset"
Omg how do I make sure every dev reads this.
The Output: Perfect
Thank you!
Last line is a banger
Great reminder to keep things simple and stop coding like you’re building Netflix.
Preach!
First 'actually does' example should be
const displayName = `${user.firstName} ${user.lastName}`;Agree with every point
Your post is awesome. It should be read during onboarding in any IT team. Nice work!
seems like you're a fan of Gary's
Absolutely agree—premature abstractions and over-engineered patterns often make code harder to maintain. Simple, readable, and pragmatic solutions almost always win in real-world projects.
preach!
The interface with one implementation section resonates. I have wasted so much time in jumping between IFoo → FooImpl only to find out that there was no second implementation.
Interfaces pay off by themselves just by including unit testing in the mix. Sounds like your code doesn't receive the unit testing treatment. Try to improve that.
LOL that name sure has a solid codebase.. Did Terrence Howard figure out how to Vibe code?
100% agree with you.
You can't optimize what you don't have.
Write it, keep it simple, when you notice patterns, OPTIMIZE.
Before then, keep it simple
You are so right, and you have one major mistake, not caused by you.
Typescript is not A language. Its a transpiler and syntax checker.
Its not Java or C#....
The TypeScript compiler is a transpiler. Typescript itself is a programming language with its own specification, grammar and type system. A language doesn't stop being a language because it transpiles -- otherwise C or Kotlin wouldn't count either.
I will not argue, just note C lang is almost every you touch..
In matter of design patterns this article mentioned, i agree.
I will personally will add a different title to this Article: Whats good for Netflix, usually isnt good for you
Typescript is a absolutely a language it's a transpiled language with its own syntax that is very similar to javascript.
Oh? And what makes typescript not a language when C, python and Java are?
I will help you find some modesty. to do so i will ask you how do you handle memory in typescript ? or how do you treat Fibers, networking, and better ask yourself: v8 (nodejs engine) is written in c, Why Netflix is writtern in Java, and not typescript? why your iphone is written i objective c, and android in java, Do you now the JVM (eg Java Virtual Machine) in order to understand kotlin and java, I asume you are a frontend dev, or even holding a fullstack title, so please enjoy your form creating jurney. Pepole like you make cybersecurity companies very rich.
" how do we handle memory in typescript? " - that's irrelevant. Manual memory management isn't required to be a programming language. Python doesn't have manual memory management either. is it not a language?
" V8 is written in C " - so?
" Netflix written in Java and not TypeScript? " - what are you rambling on about? Netflix uses TypeScript extensively for their frontend and Nodejs services. But again, irrelevant to whether TypeScript is a language.
" people like us make cybersecurity companies rich " - dude, you don't pull out an ad hominem attack when you're confidently wrong about something, it has nothing to do with what you're arguing.
your definition of "programming language" seems to be 'systems language with manual memory management.' By that definition, you've just excluded Python, Js, Ruby, PHP, C#, Java (has GC), Go (has GC), and most languages used in production today. That's not a reasonable definition.
github.com/netflix
and as you said typescript is superset of javascript. you want to call it language ? enjoy
C++ is a superset of C (mostly). Is C++ not a language?
a c/c++ developer with 5 years exp. earn 200K-300K usd year.
Java Developer earns more
Rust and low level asm earn much much more.
How much typescript developer earns?
To sum : yes you can build full stack end even api gate with nodejs and you can use typescript as a lang to do so.
And as your article says if you are not netfilx... then do what ever you want..
I guess you've completely abandoned the argument now? it doesn't look like you're arguing about what we originally were anymore.
Have a great day lol
they said: "TypeScript is not a language."
they mean: "All you TypeScript programmers are fake programmers."
they feel: "I'm above all of you script kiddies; I am the sage."
what the rest of the community see: "they're making fun of themselves."
This is not what i ment. and the discussion is out of it's context.
1) The writer spoke about design pattern, and when use them or not.
I will not argue, since design patterns are really depends on your use case,
the size and the stage of the project
Aah, do you know assenbler to understand the abstractions of C? I love C++ (and C is OK) but you really don't need it to write a user interface. "If you only have a hammer, you tend to see every problem as a nail".
I am glad you mentioned hammer and nails.
Personally i wrote my first code on Comodor 64(guess my age..)
What i want to tell you is simple.
Yes, Typescript helped filling the gap between pure javascript to OO and strick typing.
Now we are ln EcmaScript and once decoration (annotations) and more will be part of language, the advantages of typescript will be gone.
So the fact you know to consume rest apis and build amazing front end interfaces: eg(hammer) 🔨 , so everything is a nail 💅 for you.
An old man advice: i have seen trends come ane go. You want to consider yourself a developer? LEARN. You bever know enough.
TypeScript is a language, as much as any other. Some languages generate machine code, some don't, and some are transpiled into other languages. Still, they are all programming languages.
I guess your environment (Nest.js, Angular, probably) is amplifying the tendency to take an OOP deep dive. Just don't ever start with separating things, "just because" ... the separation in Services, Controller, Repository and what not ... is over-abstraction to begin with and it makes beginners questioning what's that about and they leave the straight path and never come back.
I come from a Java background and they mastered that shit.
When I see an "@" sign somewhere in the codebase it makes me sick. Just pass the damn thing around when you need it and don't rely on something "magically injecting" things.
Agree, completely. People should learn to differentiate between true services and components, and simple utilities and data types. The later absolutely must not be involved in dependency injection. Also, one must understand, that, while dependency injection helps decoupling of code, it couples the code to the DI infrastructure/framework, and in not so few ocations, that coupling can be avoided. This is applicable not just to javascript/typescript, but to other platforms like Java, .NET, Python and whervever DI is a thing
A problem outside of this is the Exec / Business Owner pushing new projects on top of the current projects. Engineering size hasn't grown. Nobody is going to spend their time improving old (not today's priority cough Ai something ) code. The reality is just get it working and move on. That's what the Execs / BO wants. New things always look better in their reports to share holders. So keep you engineers busy and only fix or refactor when pigs can fly. Something, something, profit.
Yeah! KISS keep it simple stupid!