That's an excellent question!
It will surely depend on the person, but here are some things that stand out:
One is "thinking procedurally." You have to get to be really good at breaking your problem down into steps, and then expressing them one by one. As a common example, think about explaining how to make a peanut butter and jelly sandwich, if you have to break it down as far as "Hold the jar with your left hand and the lid with your right and twist them in opposite directions" to explain how to open the peanut butter. Now imagine if you have to do that in terms of which muscles to move, and how much. Coding can be like that.
Another is learning to debug. You can write code, but it will have bugs in it. Debugging can be really, really frustrating, and you have to learn both the tools for debugging (print debugging, breakpoints, single stepping, reading stack traces, and so on), and how to apply them, which involves learning where the bugs likely lie, and the patients to really track them down.
Then there's understanding what is worth actually writing. Many times, I have put a lot of work into some new function that will do something, and then found out that someone else has already done it, and better. It's great when you're learning to write things from scratch, but sometimes (whether because you're trying to learn about a different skill, or you're at work and are trying to complete a project quickly) it's better to use a preexisting piece of code, and write something that uses it. It can be hard to figure out which things will already exist and which ones you'll need to make, and making a mistake can mean a lot of lost time, if nothing else.
Related to code but sometimes overlooked, it can be hard to actually make your programs be what they need to be. Often, code you write will be used by other people. If you think you knew what they wanted, but were wrong, you can end up having written something they don't want to use, or can't. It's important to understand what you'll actually be doing.
I am often bad at taking code from "works, but barely" to a really nice, polished and finished piece. We sometimes say in software engineering (among other places) that the last 20% of the work takes 80% of the time. It can be easy to get a prototype going, but then you start finding all the little bugs, edge cases, and tweaks you need to make, and you might get distracted, discouraged, or busy with something else before then.
Finally, there's the problem that once you realize you know how to code, you start seeing ways you could write programs to do everything, and then you spend all your time writing programs instead of doing anything. (I'm kidding. Mostly.)
Phew, that was a lot. Again, I'm sure it will be different for different people, and some of these are surely easy for some. A good thing about all of the examples I gave is that, first, you can get better at all of them with practice, which you can mostly do just by starting to code and seeing what happens, and second, you don't need to be perfect at any of them. I've been a software engineer at Google for six years, and I still make mistakes with all of these all the time. You did ask for the hardest things. Getting started on them now will absolutely serve you well.
I hope this is more helpful than it is overwhelming. Good luck!