Something that I found useful is taking the concept of katas from martial arts. Writing the "same" program multiple times, but trying to refine the parts I didn't like in the previous iteration (e.g. try writing in less code, with smaller functions, with less libraries, in a more testable way, etc).
It's specially rewarding to do this exercise with utility libraries, as it teaches you about the nuances one abstraction level below (both in terms of learning what is possible but not exposed by the abstraction and in terms of learning what pitfalls exist). Many times I find that it's more elegant to drop down one abstraction level to accomplish something than to add more abstractions on top due to lack of understanding outside my primary abstraction level.
It's specially rewarding to do this exercise with utility libraries, as it teaches you about the nuances one abstraction level below (both in terms of learning what is possible but not exposed by the abstraction and in terms of learning what pitfalls exist). Many times I find that it's more elegant to drop down one abstraction level to accomplish something than to add more abstractions on top due to lack of understanding outside my primary abstraction level.