I have been a close observer of code coming from India. I think the majority of the problems are because of vague, incomplete, and wrong requirements coming from the company that hired them. One company sent over someone. The guy spent a month talking to management, employees, probably the janitor and figured out what we actually wanted and the resulting code was decent.
This is part of the problem, and, in fact, part of the work of a software developer: given ambiguous and stupid requirements, you work with the customer to 1) make them clearer and 2) get rid of the unnecessary, complex requirements that do not bring any bussiness value. (My experience here shows though that it's not a standard thing in Indian culture to question the status quo).
There's a lot of implicit requirements that need to be taken into account when writing code: maintainability, security, performance, user experience, accessibility etc. It's better if everything is explicitly stated, but we don't live in a perfect world unfortunately. (My experience here shows that those implicit requirements are often not well executed, particularly when it comes to front-end code, due to a mix of: lacking experience, time pressure, different UX sensibilities).
Maybe it was just the firm I worked with, but they got the same requirements doc that the software was eventually written against using an American outsource firm.
The Indian firm wanted requirements so detailed that I had to tell them where to put the for loops and while loops. It was faster to just write the code myself than explain such easy details.
That is the problem with outsourcing in general: much of the work a development team do is figure out what the problem really is and how to solve it. The actual coding part is easy in comparison. But the business people didn't really see this, and....well...the results were predictable.
If a spec isn't vague, incomplete and wrong then you could just make a compiler that compiled the spec into working code and skip the programmer.
The job of a programmer is to map the vague notion of what the software should do, which is reflected in the spec, into a concrete, internally consistent set of instructions to the computer.