Another interesting way that I learned was via the Beagle Bros contests (in old fashioned BASIC with Assembler calls).
Dude!! I LOVED Beagle Bros back in the day, hammering away at their advertisement's 'one-liners' on the school's Apple ][+ and ][e's. I learned SO much from those. Our family was too poor to afford computers back then, but I had read through so much BASIC code that I knew it by heart by the time we finally got a Timex-Sinclair 1000.
I made two programs on that; a 'catch the falling object' game that got faster and objects falling farther away from you as you progressed, and a 'steer a vehicle (actually a letter "A") between the ever-closing walls' game. Both of them I saved the source, hand-written on school note paper, for years so I could show off to my friends whenever I had the chance (they all had Commodore VIC-20s and TI 99/4As, the lucky bums). That's all lost to the sands of time now...
Funny thing, that's also how I learned how RAM could affect a computer's speed. Because the Timex-Sinclair 1000 had only 1K of built-in memory, I always had to code in larger slow-down loops when programming other computers, and when my uncle bought a 16K RAM pack for his TS-1000, I thought it was the fastest computer I had ever seen.
Now, I'm learning Pascal (seems to be the only language I have the bean for anymore) and as a learning exercise, I'm turning all the text-mode tutorial programs into full GUI apps in
Lazarus, with sometimes unexpected results.