What Is Machine Code? How CPUs Execute Instructions

Machine code is the lowest-level programming language a computer can understand: raw binary instructions, sequences of 0s and 1s, that tell the processor exactly what to do. Every program you run, from a web browser to a video game, is ultimately converted into machine code before your computer’s processor can execute it. It is the final, non-negotiable language of hardware.

How Machine Code Works

A processor operates by reading one instruction at a time from memory. Each instruction is a pattern of binary digits (bits) that encodes two things: what to do, and what to do it with. The “what to do” portion is called the opcode. It might tell the processor to add two numbers, load a value from memory, or jump to a different part of the program. The “what to do it with” portion consists of one or more operands, which specify the actual values or memory locations involved.

For example, a single instruction might mean “add the number 1 to the value stored in register X0 and put the result in register X1.” In binary, that instruction is just a string of bits, perhaps 32 bits long on many modern processors. The processor doesn’t see words or symbols. It sees something like 10001011000000000000010000100001, and it knows from the bit pattern exactly which operation to perform and which data to use.

Why Machine Code Differs Between Processors

Machine code is not universal. Each processor family has its own instruction set architecture (ISA), which defines the exact binary patterns that correspond to each operation. A machine code program written for an x86 chip (the kind in most desktop PCs) will not run on an ARM chip (the kind in most smartphones and newer Mac laptops) because the two architectures use entirely different binary encodings for their instructions.

This is why software sometimes needs to be compiled separately for different platforms. The high-level logic might be the same, but the final machine code output has to match the specific processor that will run it. Even within a single architecture, different generations of chips can introduce new instructions that older chips don’t recognize.

Despite the differences between architectures, the most common operations are remarkably similar. On x86 processors, for instance, just ten types of instructions account for about 96% of all operations during typical workloads. Loads (reading data from memory), branches (deciding which instruction to run next), comparisons, stores (writing data back to memory), and simple arithmetic like addition and subtraction dominate the work a processor actually does.

From Source Code to Machine Code

Almost nobody writes machine code directly anymore. Programmers write in high-level languages like Python, C, or JavaScript, and software tools handle the translation down to binary. There are two main approaches to this translation: compilers and interpreters.

A compiler translates an entire program into machine code in one step, producing a standalone file (like an .exe on Windows) that the processor can run directly. Once compiled, that file can be executed over and over without repeating the translation. This is how languages like C and C++ typically work.

An interpreter takes a different approach: it reads the source code line by line and translates each line into machine instructions on the fly during execution. There’s no compiled file saved afterward. Every time you want to run the program, the interpreter has to do the translation again from scratch. Python and JavaScript commonly use interpreters (though modern implementations blur this line with techniques that compile frequently used code sections during runtime for better speed).

Assembly Language: The Human-Readable Layer

Sitting one step above machine code is assembly language. It uses short text abbreviations called mnemonics, like ADD, MOV, or JMP, to represent the binary instructions a processor understands. There is a direct one-to-one mapping between an assembly instruction and a machine code instruction. Writing ADD X1, X0, #1 in assembly produces the exact same binary pattern as if you had typed out the 0s and 1s yourself.

Assembly language was invented because reading and writing raw binary is brutally impractical. A program called an assembler converts the human-readable mnemonics into the corresponding machine code. Assembly is still used today in performance-critical situations, embedded systems, and security research, but it remains specific to a particular processor architecture, just like the machine code it represents.

How Processors Execute Instructions

The processor runs machine code through a repeating cycle called fetch-decode-execute. It maintains a special internal counter, the program counter, that holds the memory address of the next instruction to run. During the fetch step, the processor sends that address to main memory and retrieves the binary instruction stored there. The instruction is placed into a holding area called the instruction register.

During the decode step, the processor breaks apart the binary pattern to figure out which operation the opcode specifies and which operands are involved. Then, during the execute step, it actually performs the operation: adding numbers, moving data, comparing values, or whatever the instruction demands. After execution, the program counter advances to point at the next instruction, and the cycle repeats. Modern processors can perform billions of these cycles every second.

Reading Machine Code in Hexadecimal

When developers do need to inspect machine code, they rarely look at raw binary. Instead, they use hexadecimal (base-16) notation, which condenses the 0s and 1s into a much shorter format. Each hexadecimal digit represents exactly four binary digits. So the eight-bit binary value 00011111 becomes just 1F in hex. A 32-bit instruction that would take 32 characters to write in binary fits into eight hex characters.

This is purely a convenience for humans. The processor still works in binary. But hexadecimal makes it far easier to read memory dumps, debug compiled programs, and examine executable files. Tools called disassemblers can also work backward, taking machine code and showing the equivalent assembly mnemonics so a human can understand what a program is doing at the hardware level.

Early Machine Code Programming

Before high-level languages and compilers existed, programmers had to work directly with machine code. The earliest electronic computers, like the ENIAC built in 1942, were “programmed” by physically rewiring the machine and presetting switches for each new calculation. There was no stored program in the modern sense. Every new task meant reconfiguring the hardware by hand.

Later machines introduced punch cards and paper tape, which encoded binary instructions as patterns of holes that the computer could read. Eventually, keyboards and monitors replaced physical media, and assemblers automated the translation from mnemonics to binary. Each step moved programmers further from raw machine code, but the underlying principle never changed: at the bottom of every layer of abstraction, the processor is still reading and executing binary instructions, one at a time, billions of times per second.