A couple of other commenters have given excellent answers already.
But on the topic in general I think that the more you learn about the history of computing hardware and programming, the more you realise that each successive layer added between the relays/tubes/transistors and the programmer was mostly just to reduce boilerplate coding overhead. The microcode in integrated CPUs took care of routing your inputs and outputs to where they need to be, and triggering the various arithmetic operations as desired. Assemblers calculated addresses and relative jumps for you so you could use human-readable labels and worry less that a random edit to your code would break something because it was moved.
More complex low-level languages took care of the little dances that needed to be performed in order to do more involved operations with the limited number of CPU registers available, such as advanced conditional branching and maintaining the illusion of variables. Higher-level languages freed the programmer from having to keep such careful tabs on their own memory usage, and helped to improve maintainability by managing abstract data and code structures.
But ignoring the massive improvements in storage capacity and execution speed, today’s programming environments don’t really do anything that couldn’t have been implemented with those ancient systems, given enough effort and patience. It’s all still just moving numbers around and basic arithmetic and logic. But a whole lot of it, really, really fast.
The power of modern programming environments lies in how they allow us to properly implement and maintain a staggering amount of complex minutiae with relative ease. Such ease, in fact, that sometimes we even forget that the minutiae are there at all.
The answer is: binary, sometimes with electrical switches.
As late as the very early 1980’s, the PDP-11 could be started by entering a small bootstrap program into memory, using the machine’s front panel:

You toggle the switches to make the binary pattern you want at a specific location in RAM, then hit another button to store it. Repeat until the bootstrap is in RAM, and then press start to run the program from that first address. Said start address is always some hardwired starting location.
And that’s a LATE example. Earlier (programmable) systems had other mechanisms for hard-wired or manual input like this. Go back far enough and you have systems that are so fixed-function in nature that it’s just wired to do one specific job.
Hole punch cards are an automated form of that. Punch the holes manually, feed the deck through an automated feeder.
It’s a storage medium that you can write to by hand.
Binary. Now go to sleep.
scared voice i think i saw a 2
There’s no such thing as 2.
1
Its part of the bootstrap paradox.
0
- Mechanical engineering during the Ada Lovelace days
- Electrical engineering during the Z3/Turing/Manchester Computer/Grace Hopper days
There is something to using the 8085 educational kit and feeding the values into RAM, via the HEX keyboard, that lets you connect the dots pretty easily.
They just typed 1s and 0s until something happened.

I’m imagining compilers evolving from digital primordial goo.
That would be fun, making a genetic algorithm-based compiler.
By handwriting raw lambda calculus.

compilers were pretty early on







