Why Doesn’t Software Work Without Installation? A Visual Guide to How Apps Hook into the OS

If you use a PC or smartphone, you’ve probably bumped into these little mysteries more than once.

  • Slack and VS Code only work after you install them, but Gmail and Google Docs run the moment you open a URL ─ what’s the difference?
  • The world is full of “install” commands ─ pip (Python), npm (Node.js), brew (Mac), apt (Linux), winget (Windows). What are all of those actually doing?
  • Why do you need to type your admin password every time you install something?
  • On macOS you usually drag a .app into a folder and you’re done, while Windows .exe installers walk you through dialog after dialog. Why the gap?

“Installation” is a single word that hides a lot of different feelings, which makes it hard to summarize. But once you trace each one back to its root, they all share the same answer.

For software to run, it has to formally introduce itself to the OS (Windows, macOS, Linux, etc.) ─ “Hi, a new app is moving in” ─ and shake hands with it.

“Installation” is just the name we give that handshake ceremony. In this article we’ll unpack:

  • What is actually inside an executable file (.exe / .app / .deb)
  • Why programs cannot run on their own and always need help from the OS
  • What the “installation” black box is concretely doing behind the scenes
  • Why “no installation needed” worlds (web apps, portable apps, containers) can exist at all

We’ll take it step by step with diagrams, assuming zero prior background.

💡 Tip

We previously published What Is an IP Address? If network “addresses” are the foundation on the internet side, the OS-and-app handshake covered here is the foundation on the device side. Understanding both gives you a connected picture of how data reaches your machine and how the app on it actually runs.

1. What programs actually are ─ executables as instructions for the OS and CPU

What exactly is in that .exe, .app, or .deb “package” you just downloaded? Once we crack that open, it becomes clear why programs cannot run on their own.

1-1. An executable is just bytes ─ but bytes in a very specific order

If you peek at an executable, all you see is a long stream of zeros and ones (binary). It looks unreadable in a text editor. But the order of those bytes follows strict rules, and when the OS reads them, meaning emerges: “the program body starts here,” “this region is for data,” “this section lists external libraries,” and so on.

This OS-readable layout is called an executable file format. Each major OS uses one of three formats, and every PC in the world uses one of them.

OSExecutable formatCommon file extensions
WindowsPE (Portable Executable).exe / .dll
macOSMach-O (Mach Object)no extension / executables inside .app bundles
LinuxELF (Executable and Linkable Format)no extension

So when a Windows .exe refuses to run on a Mac, the reason isn’t malice ─ the OS just doesn’t speak the same instruction-format dialect. It’s a bit like sending a Japanese letter to a French reader: they can see the characters, but they can’t follow the meaning.

1-2. The four main parts inside an executable

The three formats differ in detail, but their high-level structure is similar. Splitting it into four “rooms” makes things much easier to picture.

┌─────────────────────────────────────┐
│ Header                              │ ← cover page the OS reads first ("ELF v1.0", "64-bit", target OS)
├─────────────────────────────────────┤
│ Code (.text)                        │ ← the CPU instructions themselves
├─────────────────────────────────────┤
│ Data (.data / .rodata)              │ ← strings, constants, embedded resources
├─────────────────────────────────────┤
│ Symbols / link info                 │ ← list of "which external library and which function I'd like to call"
└─────────────────────────────────────┘
  • The header is the cover page. The OS reads it first and decides “is this even a kind of file I can run?”
  • Code holds the actual CPU instructions ─ add, mov, jmp, and so on.
  • Data stores the program’s raw materials: text strings, numerical defaults, image resources.
  • Symbols / link info is a “shopping list of missing parts” ─ “I want to call printf here, please connect it for me,” it tells the OS and the loader.

See the picture? An executable is not a finished product. It’s a bag containing assembly instructions + raw materials + a shopping list. The OS reads the instructions, hands the code to the CPU, and wires up the missing parts (shared libraries) to make it actually run.

💡 Tip

Don’t worry if the words “symbol” and “link info” sound intimidating. Just translate them as symbol (a label attached to a named function or variable inside the program) and link (the act of wiring up missing parts either at build time or at run time). We won’t go deeper than that here.

2. Programs do not run alone ─ the constant conversation with the OS

As §1 showed, an executable carries a “shopping list.” Where does it shop? The OS and its standard libraries. Whenever an app prints a character on screen, opens a file, or talks to the network, it isn’t doing the work itself ─ it’s politely asking the OS to do it.

2-1. The four-layer stack: app → standard library → kernel → hardware

Roughly speaking, the inside of a PC stacks up like this.

┌─────────────────────────────┐   ← Slack, VS Code, the apps you launched
│   Application                │
├─────────────────────────────┤
│   Standard library           │   ← C standard library, .NET, libc, Foundation, etc.
│   (language runtime)         │      The translation layer between app and OS
├─────────────────────────────┤
│   OS kernel                  │   ← Windows kernel, Linux kernel, Darwin
│                              │      Master of screen, files, network, memory
├─────────────────────────────┤
│   Hardware                   │   ← CPU, memory, SSD, GPU, network card
└─────────────────────────────┘

When an app wants to “show ‘Hello’ on screen,” it doesn’t fire signals at the monitor itself. A relay race happens:

  1. The app calls a function such as printf("Hello")
  2. The standard library (libc on C, BCL on .NET) prepares a request to the OS for “screen output”
  3. It hands that request to the OS kernel ─ this hand-off is called a system call (an official “work order” from the app to the OS)
  4. The kernel goes through the display driver and tells the hardware what to render, and the letters appear

In other words, apps are written from the start with the assumption that the OS will help them. There’s no such thing as an executable that runs without help from an OS. That’s the deep reason “you install an app for a specific OS” is even a concept.

2-2. System calls ─ the official “order forms” to the OS

A system call (the dedicated counter where apps say “please do this” to the OS) typically asks for things like:

What the app wantsRepresentative system call (Linux name)What happens
Open a fileopen()The OS talks to the SSD, then returns a “ticket” (file descriptor) if it can be opened
Get more memorymmap() / brk()The OS slices off some free memory and assigns it to the app
Connect to the networksocket() / connect()The OS drives the NIC and tries to reach the remote host
Exitexit()The OS cleans up (frees memory, etc.) and ends the process

Each one is a job the app cannot do, or is not allowed to do, on its own, and so it asks the OS instead. Without the OS’s help, an app cannot draw a single character on screen.

💡 Tip

The “installation” we explore in §3 boils down to “setting things up so the app can place these orders correctly with the OS”. Keep this §2 picture in mind and §3 will land much more naturally.

3. [Core] What “install” actually does behind the scenes

By now we know “an app cannot run without the OS’s help.” That makes installation easy to summarize: it is the umbrella term for the prep work that brokers a relationship between the app and the OS. Almost every installer in the world (.msi, .pkg, .dmg, .deb, .rpm, the .app drag on macOS, the install button in Microsoft Store, etc.) walks through the following six phases.

3-1. The six phases an installer runs through

Each phase takes a few hundred milliseconds to tens of seconds, and the sum of them is what you experience as “the install wait time.”

  1. 1Signature checkVerifies the file is “really from a legitimate author” (think of it as a stamp/seal check). On Windows it’s Authenticode, on Mac it’s Gatekeeper, on Linux it’s GPG signing on packages.
  2. 2ExtractionUnpacks the single bag (the installer) into the actual main binary, support libraries, configs, docs, etc. Up to here it is just “taking the materials out.”
  3. 3PlacementMoves the unpacked files into the OS’s standard locations. On Win that’s C:\Program Files\, on Mac /Applications/, on Linux /usr/lib/ and /usr/bin/.
  4. 4RegistrationAdds the app’s name to the OS’s “address book.” Windows writes to the registry (the giant scratchpad where the OS keeps settings), Mac writes plist files, Linux drops a .desktop file.
  5. 5IntegrationAdds icons to the Start menu / Launchpad / app menu, and associates the app with the relevant file extensions (.txt, .psd, …).
  6. 6Post-setupRegisters any background services, builds first-run caches, adjusts PATH, creates shortcuts, and finally cleans up temp files.

Mnemonic: verify → extract → place → register → integrate → post-setup. Every installer takes you through these six steps to get from “downloaded” to “ready to use.”

3-2. Before / After ─ the filesystem visibly changes

If the “six phases” still feel abstract, comparing the disk before and after the install makes it immediate.

Right after download

~/Downloads/
└── SuperApp-1.2.3.exe   ← single bag

After installing

C:\Program Files\SuperApp\
├── SuperApp.exe                ← main binary
├── core.dll, render.dll, ...   ← parts (shared libraries)
└── resources\                  ← images, translations

C:\Users\me\AppData\Roaming\SuperApp\
└── settings.json               ← user settings

Registry HKLM\Software\SuperApp
├── InstallPath = "C:\Program Files\SuperApp"
└── Version = "1.2.3"

Start Menu → SuperApp (shortcut)

As you can see, installation is the combined act of “splitting one .exe into the right folders + telling the OS about it.” It’s not that “double-click won’t run anything” ─ rather, in the seconds-to-minutes after the double click, a great deal of OS-level setup is happening. That’s the more accurate mental model.

⚠️ Common pitfall

You may have heard “moving the install to a different drive makes things lighter,” but Windows’ C:\Program Files\ and macOS’s /Applications/ are baked into many apps’ assumed locations. Forcing a custom path can break OS integration (§5) so badly the app refuses to launch. Sticking with the default path is almost always the right call.

4. Dependencies ─ why a single executable does not stand alone

“It’s just one app, why are there hundreds of files?” The answer lives in this section. The key is the concept of shared libraries (parts boxes that many apps borrow from).

4-1. What a shared library is

“Save this image as JPEG” is the kind of feature Photoshop, the VS Code extensions, and Slack all want at some point. If every app shipped its own copy:

  • The disk balloons with duplicate parts
  • A single security fix forces every app to be re-released

To avoid that pain, the OS provides a way to collect common parts in one file and “lend them out” to multiple apps. That’s a shared library (extension .dll on Win, .dylib on Mac, .so on Linux).

                 ┌──────────────────────────┐
                 │      your application      │
                 └──────────────────────────┘
                       │   │   │
        ┌──────────────┘   │   └──────────────┐
        ▼                  ▼                  ▼
   libjpeg.dll        libssl.dll         libcurl.dll
   (JPEG parts box)   (crypto parts box) (HTTP parts box)
        │                  │                  │
        └──────────────────┴──────────────────┘
                           ▼
                ┌──────────────────────┐
                │   OS API (kernel)    │
                └──────────────────────┘

Even if the app itself is only a few MB, in practice it depends on a handful of these “parts boxes,” and the installer drops them onto the disk along with the main binary. That’s why “one app” can take up hundreds of MB once installed.

4-2. DLL Hell ─ when shared parts start fighting over versions

Shared libraries are practical, but they have a famous failure mode: if versions don’t line up, everything goes sideways at once. This is the legendary DLL Hell (the chaos when two apps demand different versions of the same parts box and end up breaking each other).

   App A  ─→ requires libfoo v1.0
   App B  ─→ requires libfoo v2.0
                    │
                    ▼
        Only one libfoo can sit in the OS
        →  one of them is guaranteed to break

Modern operating systems mitigate this in different ways:

  • Windows: WinSxS (Side-by-Side) and Microsoft.UI.Xaml-style packages let multiple versions co-exist under different file names
  • macOS: encourages each .app to bundle its own copy of the .dylibs it needs
  • Linux: package managers (covered later) resolve the dependency graph for you

Even so, when you dig out an old piece of software and see MSVCR100.dll not found, that error is the lingering shadow of this very problem.

💡 Tip

“So why don’t we just ship a single self-contained binary that includes everything?” That’s exactly the philosophy behind modern Go binaries and Electron apps. The trade-off is bigger disk usage. Remember the choice as “share to save space, or self-contain to stay stable”.

5. OS integration ─ file associations, menu entries, autostart

In phases 4 and 5 of §3 (registration and integration), the installer makes the app officially announce itself to the OS. Looking at exactly “what gets written where,” side by side across operating systems, makes the differences obvious.

5-1. What gets registered, OS by OS

What is registeredWindowsmacOSLinux
Install ledgerRegistry (HKLM\Software\<App> ─ the giant scratchpad where the OS keeps settings)plist (/Library/LaunchAgents/*.plist ─ the macOS equivalent scratchpad).desktop file (/usr/share/applications/*.desktop ─ the Linux equivalent scratchpad)
Menu entryStart menuLaunchpad (auto-collected)App menu (auto-built from .desktop files)
File associationsRegistry HKCR\.psd → Photoshop.exeCFBundleDocumentTypes in Info.plistmimeapps.list
AutostartStartup folder / HKCU\...\Run / Task SchedulerLaunchAgents / LaunchDaemons (plist)systemd units / autostart/*.desktop
Background servicesService Manager (services.msc)LaunchDaemons (plist)systemd units

“Apps may look the same on the surface, but the scratchpad they actually write into is different on every OS.” Once that clicks, it’s no surprise that “the software ran fine on Windows but feels like a different beast on macOS.”

5-2. Why “just dropping the executable somewhere” is not enough

You can leave something like notepad.exe in Downloads and double-click it; technically it’ll run on its own. But you won’t get experiences like:

  • Quick launching from the Start menu
  • Double-clicking .txt opening it in that app automatically
  • One-click uninstall from the OS uninstall screen
  • Automatic updates running on their own
  • The same icon being there after a reboot

All of those are the fruit of OS integration. Installation became a “place files + register them with the OS” combo precisely to enable that experience.

6. Permissions and security ─ why it asks for your admin password

“Why does it ask for my password every single time I install something?” That little bit of friction is actually proof that the OS’s safety latch is working. Once you understand the reasoning, the question stops feeling annoying.

6-1. System area vs. user area

An OS’s filesystem is split into places that require admin rights to write and places that don’t.

┌──────────────────── Admin rights required ──────────────────┐
│  Windows : C:\Program Files\, C:\Windows\, HKLM\registry    │
│  macOS   : /Applications/, /Library/, /usr/local/           │
│  Linux   : /usr/, /etc/, /opt/                              │
└──────────────────────────────────────────────────────────────┘
┌──────────────────── User rights are enough ─────────────────┐
│  Windows : %USERPROFILE%\AppData, HKCU\registry             │
│  macOS   : ~/Library/, ~/Applications/                       │
│  Linux   : ~/.local/, ~/.config/                            │
└──────────────────────────────────────────────────────────────┘

During phase 3 (placement) of §3, an installer that wants to drop the app into the system area will be stopped by the OS asking, “are you sure you want this?” That’s exactly:

  • The Windows UAC dialog (“Do you want to allow this app to make changes to your device?”)
  • The macOS password prompt
  • The Linux sudo password challenge

And the symmetric case ─ “drag a Mac .app into your personal ~/Applications/ and no password is asked” ─ falls out of the same logic.

6-2. Signature verification ─ the stamp-of-authenticity check

On top of permissions, the OS also pre-checks “is this installer really from the author it claims to be?” This is signature verification (the “stamp/seal” check that confirms the installer is the real thing from a legitimate author), and each OS has its own version:

OSMechanismWhat is checked
WindowsAuthenticode / SmartScreenDigital signatures on .exe / .msi
macOSGatekeeper / NotarizationApple Developer ID signatures on .app / .pkg
LinuxPackage manager GPG keysPublisher signatures on .deb / .rpm

Warnings like “this app is from an unidentified developer” or “this app cannot be opened because it could not be verified” are exactly the result of these signature checks. Failing the check doesn’t automatically mean malware ─ small independent developers often skip code-signing because certificates are expensive ─ but “don’t run anything that fails the check” is the safe default.

💡 Tip

When you say “security,” you may also be thinking of sandboxes (the mechanism that traps an app inside a “private room” away from the rest of the system). A sandbox is protection at run time; signature verification is protection at install time. They live in different layers, and you need both. Sandboxes connect directly to the next section’s containers and web apps.

7. The “no installation needed” world

So far we’ve seen “installation = handshake with the OS.” If something else does that handshake on your behalf, then in principle you don’t need to install anything yourself. And in practice, modern computing has multiple ways to do exactly that. Let’s quickly see how each one pulls it off.

7-1. Four approaches at a glance

ApproachLaunch prepOS integrationIsolationEase of uninstallExamples
Web appJust open a URLNone (runs inside the browser)High (browser sandbox)Close the tabGmail, Google Docs, Figma
Portable appUnzip and launchAlmost none (no file associations or menu entries)Low (same as a normal app)Just delete the folderSysinternals, the portableapps.com family, some OSS tools
ContainerLaunch with docker run etc.None (runs in a sealed box separate from the host OS)High (files and network are confined to the box)Delete the containerDocker, Podman, apps on Kubernetes
Package managerOne command lineYes (the §3 six phases run automatically under the hood)Same as a regular installOne commandapt, brew, winget, pip, npm
Native install (for reference)Click through the installerFullLowControl Panel etc..msi, .pkg, .dmg

7-2. Why each approach gets away with “no install”

Each one explained in plain terms, with a beginner-friendly analogy attached.

  • Web app: the actual program lives on a remote server, not on your PC. Your PC is only running a browser. Because the browser itself has already shaken hands with the OS, the apps that ride on top of it don’t need their own handshake (think of it as borrowing a table at a restaurant).
  • Portable app (think of it as the whole app squeezed onto a USB stick): it carries all the parts it needs and avoids touching the registry / plist, so unzipping is enough. The trade-off is bigger size and no OS integration (no menu entries, no file associations).
  • Container (think of it as a sealed, portable little room that holds the app together with the bits of OS it needs): not just the app, but the libraries and config it relies on are packed into the room and run isolated from your host OS. The handshake takes place inside the room, so almost nothing is written to your actual PC. This is exactly the convenience that made “ship the whole runtime, not just the code” the cloud-era default.
  • Package manager (it automates the “install” routine that you’d otherwise do manually, on the OS’s behalf): it looks like “no installation,” but really it’s just running the §3 six phases for you with a single command. It’s better thought of as “installation, but well-automated.” During the few seconds of brew install nginx, the verify / extract / place / register dance is happening behind the curtain.

Once you see this, “do I need to install or not?” reduces to “who is doing the OS handshake on my behalf?” For web apps, the browser; for containers, the container engine; for package managers, an automation script. Someone is always doing the handshake somewhere.

💡 Tip

Even with Docker, there’s a layered structure: Docker itself has to be installed onto your OS once, but every app you run on it then arrives “without being installed on your PC.” “No install” never holds for the entire world ─ somewhere, someone has shaken hands once.

8. See it in practice ─ commands that show what installation actually changed

Now that the theory is clear, let’s open up your own PC and confirm “yes, files really do get added during installation.” Each OS has a one or two line command for this.

8-1. List installed apps / packages

First, let’s print “what is currently installed on this PC” per OS.

list-installed
# Windows (PowerShell)
Get-Package | Select-Object Name, Version | Sort-Object Name

# macOS (apps installed via Homebrew)
brew list --versions

# Linux (Debian / Ubuntu)
dpkg -l | head -20

8-2. Inspect what files a single app put down

You can also drill into a single package and see exactly which files its installer scattered across the system.

inspect-installed-files
# Linux (Debian / Ubuntu) ─ list every file the git package placed
dpkg -L git | head -20

# macOS ─ inspect a package installed via the official .pkg path
pkgutil --pkg-info com.apple.pkg.CLTools_Executables

# Windows ─ list installs via the registry Uninstall keys
Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' | Select-Object DisplayName, DisplayVersion

You’ll see entries like /usr/bin/git, /usr/share/man/man1/git.1.gz, /etc/bash_completion.d/git ─ in other words one app spreading itself across many places in the OS. Realizing “that one downloaded file actually seeps into the OS this widely” is what makes installation feel real instead of magical.

Wrap-up ─ four lines that capture it all

Long article, but its essence fits in four bullet points.

  • An app cannot run alone. It runs only by borrowing the OS and the hardware ─ which is why a handshake protocol with the OS is unavoidable.
  • Installation is the ceremony that performs that handshake ─ verify, extract, place, register, integrate, post-setup, six phases in order.
  • The admin password is “are you sure you want to write into the system area?” The signature check is “is this really from a legitimate author?”
  • Web / portable / container / package manager are all “proxies” for the handshake ─ “no install” only works because someone, somewhere, has already shaken hands.

With these four in mind, you can read .exe, .app, .deb, or docker run through the same lens and understand what is happening in front of you.

For the network-side foundation, see our previous What Is an IP Address?. Combine it with today’s “OS-and-app handshake” and you have the full picture of how data reaches your device and how the app on it actually runs. To go deeper still, Choosing the Right Numeric Type in SQL on this site walks the same kind of “how data is represented inside a program” thread.

FAQ

Q1. What is the difference between a portable app and an installed app?

A. The actual binary inside is almost the same; what differs is whether OS integration happens. An installed copy runs all six phases of §3 and “shakes hands deeply” with the OS, so you get Start menu entries, file associations, automatic updates, and so on. A portable copy stops at phases 1–2 (verify, extract) plus the placement of the folder ─ it deliberately skips having its name added to the OS’s address book.

Q2. Does uninstalling really remove everything?

A. Most of it disappears, but settings and user data are usually left behind. Windows leaves config under C:\Users\<you>\AppData\, Mac under ~/Library/Application Support/<App>/, Linux under ~/.config/<app>/ ─ on purpose, so a future re-install can pick up your preferences. If you want the full nuke, delete those folders manually or use a helper such as AppCleaner (Mac) or Bulk Crap Uninstaller (Windows).

Q3. Why does drag-and-drop work for macOS .app bundles?

A. A .app is not really a “file” ─ it’s a folder packed with everything the app needs (we call this a bundle). The main binary, the parts (.dylib), the resources, and Info.plist are all inside, so very little registry-style external editing is needed. Dropping the folder into /Applications/ is enough for the OS to recognize it. Windows .exe binaries can be run standalone too, but the Windows convention scatters their settings into the registry, so a single drag isn’t enough to leave a tidy install.

Q4. Does running Docker count as “installing”?

A. Think of it as two layers. Docker itself (the engine that runs containers) does need a one-time native install on your PC. Once it’s there, every individual app you run on top of it lives inside a container (“private room”) and never gets written into your real OS. “Apps that look install-free” are install-free precisely because the Docker engine is doing the OS handshake on their behalf.

Q5. Can I install software without admin rights?

A. Yes, as long as you stay in the user area. On Windows there are “user installers” that drop into %LOCALAPPDATA% (VS Code’s “User Installer,” for example), on Mac you can drag a .app into ~/Applications/, and on Linux you can copy binaries into ~/.local/bin/ or use pipx install --user. But anything that affects the whole system (driver installation, persistent background services, dropping files into /usr/local/, and so on) inherently needs to write into the system area, so admin rights become unavoidable.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *