There are two other mechanism I know off the top of my head.
One is to put some circuitry on the cartridge, and then arrange the usage of tiles such that all the background tiles are in one bank and all the foreground tiles are in another. If you do this right, you get an address line which cycles high and low once each scanline–and you can put a circuit on the cartridge which counts the scanlines, triggering an interrupt. This was only available if you could put that circuitry (usually, a “mapper”) on the cartridge.
The last method is to count cycles.
I would note that among other differences, this is much, much easier on a Game Boy. The Game Boy has a register you can read which tells you which row you are on. Not the only thing that’s easier on a Game Boy—there’s also a hblank interrupt, and the tilemap is larger than the screen. Anyone interested in NES programming but unsure about how much they like dealing with obscure technical problems may want to try Game Boy programming first to get a taste for it.
If you've ever noticed some "weird flickering" near status bars in NES games, this is probably the culprit: Few very skilled developers were able to pull it off cleanly.
Imagine a text file pushed out theough a serial port, all the data is just dumped on to the wire and the other end worries about interpreting carriage returns and line feeds. You just imagined how old line printers worked!
When the TV wasn't able to figure out the sync signal you'd get rolling [1] or tearing [2] where the picture was being displayed the best the TV could make out.
1: https://youtu.be/bGXEqzCS4nE?t=28 2: https://www.youtube.com/watch?v=FVOVk3psy-w