Every application, game, or other piece of software has a target platform, which describes on what platform the software can be run. There are various target platforms, and various types of, such as operating systems (Windows, macOS, Linux, BSD, Android, iOS), CPU architectures (x86, x64, ARM), and even target other applications such as web browsers. In this article, we'll learn why this happens and how it works.
Hardware Platforms Targeted by Operating Systems
In order for a software to be usable by a human being, it requires input devices, such as the keyboard and mouse, and output devices, such as the monitor and speakers. These hardware communicate via the motherboard with the CPU ., of which there are several architectures, such as x86, x64, and ARM.
The minimum software a user needs is an operating system, which targets a CPU architecture as its platform. Targeting in this case means that the programs of the operating system were compiled to machine code in a format that a x86 CPU can understand, or in a format that a x64 CPU can understand.
In most cases, architectures are incompatible with each other, which means a x64 operating system can't run on a x86 CPU. Exceptionally, a x64 CPU can run x86 programs.
Operating Systems Targeted by Applications
Applications tend to target operating systems instead of hardware, such as Windows, macOS, Linux, BSD, Android, iOS, etc. In order for the application to access the keyboard for input and the monitor for output it must pass through the operating system, which means it must be able to communicate with it, to interface with it.
Different operating systems have different interfaces they expose for the applications to use, also called Application Programming Interfaces, or API's. This means that an application made for one operating won't be able to interface with other operating systems as the communication won't be formatted appropriately.
For example, on Windows, an executable file would be a file with an .exe file extension. On Linux, extensions aren't as common, but the format for a native executable is called ELF.
To have a simple idea of what this means, the first four bytes of an ELF file must be 0x7f (which is equal to the bits 0111 1111), then the letters E, L, and F encoded in ASCII. This signature lets Linux quickly check if something is really an executable file when you try to execute it. Naturally, an executable on Windows won't have a signature that spells ELF in it. Instead, the .exe file (a Portable Executable or PE) is likely to start with MZ in ASCII instead, which are the initials of Mark Zbikowski, a developer of the MS-DOS, an operating system by Microsoft that predated its modern Windows.
Besides the signature, there are various sections in the file which may be different from operating system to operating system. However, there will also be a section containing the algorithm of the program, and this algorithm will be in machine code, which means it will be exactly the same bytes in any operating system for a given architecture, so long as the algorithm doesn't make use of the operating system's API to do anything. For example, adding 2 + 2 is going to be the same machine code, so exactly the same bytes in the executable file.

In other words, ELF and PE are essentially packages. The developer packages the machine code in the ELF format or the PE format, and Linux only accepts ELF-packaged machine code, while Windows only accepts PE-packaged machine code.
Cross-Platform Applications and Abstraction Layers
An application is said to be cross-platform if it can be compiled for different operating systems from the same source code. What this means, in practice, is that most of the program doesn't depend directly on the operating system interface, but instead depends on a subprogram called an abstraction layer.
The abstraction layer exposes an unified interface for most of the program that is typically the minimum common denominator of all operating systems that the application intends to support, and then translates commands it receives from other subprograms into the appropriate command for each operating system.
As it's not possible for a single executable file to work natively on all operating systems, in order for an application to be cross-platform there must be some setting that the developer changes that makes the abstraction layer change which platform to target.
For example, when the application is compiled for Windows, the abstraction layer uses the Windows API, so it can't run on Linux. When the application is compiled for Linux, the abstraction layer uses the Linux API, so it can't run on Windows. This is true even if we somehow extracted the machine code from the PE or ELF package and put it in the other type of package, because the use of one system's API changes what machine code is put in those packages.
In practice, most applications and games don't implement their own abstraction layer unless they are doing something very special. Instead, they rely on third-party GUI toolkits (e.g. Qt, GTK), game engines (e.g. Unity, Godot), and other libraries (e.g. SDL) to handle the peculiarities of each operating system.
For example, let's say that you want to open a file. On Windows, a filepath looks like this: C:\folder\file, but on Linux it looks like this: /folder/file. The directory separator is different and that's something your program would have to deal with. But if you use Qt or GTK, these and other peculiarities are handled for you by the libraries, so you don't have to worry about it, so long as your program only interfaces with them, and not with the operating system directly.
Emulators
It's sometimes possible to run an application that targets one platform in a different platform through emulation and the insertion of a translator layer that act as platforms on top of platforms.
For example, WINE is a program that implements the interface to work on Linux, and exposes an interface similar to the Windows operating system. This means we can put WINE on top of Linux, and then a Windows application on top of WINE, and it WINE will "translate" the Windows commands to Linux commands.
Runtimes as Target Platforms
There are also applications that target other programs as their platform. In this case, the program targeted is called a runtime, such as the Java runtime and the .NET (dot net) runtime.
The idea in this case is that instead of compiling your program directly to an operating system and CPU's machine code, you compile it to an intermediary format called byte code. The user then uses a program called the runtime to run the application. This runtime translates the byte code to equivalent machine code for the user's machine when the program is run.
I guess that's why it's called a "runtime," because the machine code is created at "runtime," instead of at "compile time."
What this means is that now you can have a single .jar file that works on both Linux and Windows, and even on both x64 and ARM architectures, so long as the user has Java installed in their system.
You would think that runtimes would solve the fragmentation of platforms once and for all, but in practice it feels like it's the complete opposite. That's because new CPU architectures rarely happen, and we only have a handful of operating systems people actually use, but new versions of runtimes are released every year.
In order to run a .jar compiled for the Java version 8 runtime, you need Java 8 installed. But Java 8 can't run a .jar compiled for Java 16, so now you need Java 16 installed.
Fortunately, each new version is (generally) backwards compatible. That's kind of the point. This means that if you have the latest version of Java you should be able to run a program compiled for Java 1, no matter how old it is. Java will translate the instructions in the byte code to their modern equivalent.
For the record, C# program compiled for .NET runtime become .exe files but they can be run on Linux if you have the runtime installed and they don't use anything specific of Windows, but that is rather unlikely. Any C# program that displays a window is probably using a Windows specific API such as WinForms or WPF, so some translation layer would be required for them to work on Linux. Java doesn't have this problem because it has its own GUI toolkit (or rather, multiple of them, such as Swing and JavaFX).
Interpreted Programs
Some programs are made with interpreted programming languages (also called scripting languages) like Python and Ruby. These programs don't need to be compiled to machine code or byte code. They can be distributed source code, and the source code is interpreted by a Python or Ruby interpreter installed in the user's computer.
In this case, you would have a standard library in the language that provides an interface for common operating system level operations like opening a file. When a Python script says it wants to open a file, it interfaces with the Python's standard library, and this library interfaces with the user's operating system, acting as an abstraction layer between the program and the operating system.
So long as nothing of a particular operating system is required, a Python script runs on any platform that can have a Python interpreter. Although it's worth noting that there are several different version of Python and Ruby, just like runtimes, and they're generally backwards compatible, just like runtimes.
Applications as Target Platforms
Finally, some applications target other applications as their target platform, which is some peak platforming, to be honest.
Web Browser as the Target Platform
Many applications nowadays target the web browser by being distributed as a web page. This means that any operating system that has a web browser is able to run the web application (or browser game) without any effort being required by the developer to make the application cross-platform.
In other words, the web application isn't really cross-platform—its platform is only one: the browser. It just happens that the browser is cross-platform, which makes the web application cross-platform as a consequence.
Conversely, this creates a bit of a problem because unlike runtimes and scripting languages, there are different web browsers in the market, and unfortunately they don't actually all work exactly the same way. Furthermore, a web browser may break backward compatibility with existing webpages on the entirety of the web for all sorts of reasons, mainly due to security and privacy concerns. And these can be very unexpected.
For instance, it's not possible for the Javascript program of a webpage running inside a web browser to tell what color a link on the page is. That's because web browsers make visited links purple by default, so if they could tell, that means any webpage could figure out if you have accessed a specific webpage of another website by creating a link to it and checking its color: if it's purple, you visited it. Consequently, when a Javascript program tries to do this using the browser's API, the web browser just lies to it and says the link is blue even if it's not.
There's countless other cases like this, and although it's generally harmless, you never know what's going to break in a future browser version.
Additionally, Javascript programs don't have direct access to the operating system, meaning they can't actually open any files by their filepaths. They also don't have direct access to the clipboard, or direct access to pretty much anything. That's why you often get a notification asking for permission in the browser when the webpage tries to access something special.
Consequently, lately there's been the practice of turning webpages into applications using tools like Electron. What this means is that instead of telling the user to access a webpage with their browser, you tell the user to install your browser that is a special browser that can only open a single webpage. As such, all users access the webpage through the same web browser, so the webpage always works as expected, and because this is a special-purpose browser it can allow the webpage to have direct access to the filesystem like a normal application would, and the webpage doesn't need to be online, it can just be all inside the browser to begin with. In other words, Electron is used to package a web page inside a Chromium browser.
Players as Target Platform
Some applications require special-purpose applications in order to run, sometimes called players. This is similar to how runtimes work, except that in this case the play is an actual application that opens a file—the Java runtime is just program without a graphical interface of its own.
An example are Flash applications, movies and games (.swf files), which require a Flash player, which used to be called Macromedia Flash Player before Adobe bought them. These were popular in the past, Newgrounds being host of countless of them. They disappeared because people mistakenly believed that HTML5 could replace Flash.
It's been a decade and I'm still waiting for those "HTML5 animations" that are purportedly better Flash animations. I blame SVG for this.
A more peculiar example are text adventure games (or Interactive Fiction, IF) in formats like Glulx that need a player like Gargoyle in order to be played. It's worth noting that because these games are just text they are extremely small in file size.
As you may imagine, "players" made more sense in the past when Internet speeds were very slow and auto-updating software were unusual. A game that is only a few kilobytes of data can be quickly downloaded compared to the runtime or player that is hundreds of megabytes. This is a stark contrast with how things are nowadays: we take a webpage that is only a few kilobytes and package it into a web browser that is hundreds of megabytes and ship that instead because everyone has infinite disk space now.
An even more intriguing example is PICO-8 [lexaloffle.com/pico-8.php], which is a completely fabricated retro game console that runs in the web browser for which you can create your own game "cartridges" for using the Lua scripting language.