Ok, sometimes it can be crappy engineers. Or, more commonly in my experience, it’s contractors. If you know you’re not going to be responsible for the code long-term, you’re less likely to expend the effort to ensure it stands the test of time. Their motivation is to get it to the point where they get paid; they don’t care what happens to it. Even if you have a senior engineer supervising their work, they can’t catch everything, or they may be thinking “it’s temporary code, I’ll fix it later” (see: TODO). Short term contractors also compound the problems of more frequent new engineers with different styles.
Experienced engineers will try to anticipate the direction of the codebase and ensure that code written today will accommodate future use cases (this is a slippery slope: engineers have a tendency to spend too much time generalizing code to handle elaborate use cases that never materialize). You can help out your engineer by making them aware of future plans that they may want to think about when implementing their current workload. It could save a lot of re-work later.
This is one of the big differences I’ve seen (and participated in) between large companies and small companies. Large companies generally have less tolerance for imperfect code, at the expense of taking much longer to get things done. Startups have to get things done fast in order to survive, and getting something working fast may be preferable getting something working perfectly. That may be the right business decision, but keep in mind that you’ll pay for it later on.