In this article

Image source: Depositphotos
A machine that felt fast in January and sluggish in September usually hasn’t aged. Something specific changed, and it’s almost always one of four things: the system drive crossed the line where the SSD controller stops keeping up, memory pressure is forcing constant compression and swap, the cooling can’t hold clocks under sustained load, or a background scanner is walking every file your build touches.
The useful part is that you can tell which one in about a minute, and the fix for each is different. Adding RAM does nothing for a thermal problem. Cleaning caches does nothing when Windows Defender is scanning 40,000 files in node_modules on every install. So measure first.
Summary
Diagnose before you change anything. Check free disk space first: SSDs use spare capacity as a write cache and for garbage collection, so a drive past roughly 80-90% full slows down for reasons no amount of RAM will fix. On macOS read the memory pressure graph, not “Memory Used” - green means fine even at 90% used. Check for thermal throttling with pmset -g thermlog on macOS or sensors on Linux before blaming the CPU. The biggest disk offenders on a dev machine are Docker’s builder cache, Xcode DerivedData, and language package caches, none of which show up where you’d look. And exclude your build directories from real-time antivirus scanning, which is often the single largest unforced slowdown on Windows.
What really consumes resources
Open the process list before you form a theory. macOS has Activity Monitor, Windows has Task Manager plus the more detailed Resource Monitor, and Linux has htop, btop, or plain top. Sort by memory, then by CPU, then look at the disk column.
Read the right number, though. On macOS the metric that matters is the Memory Pressure graph at the bottom of the Memory tab, not the “Memory Used” figure. Modern operating systems deliberately fill RAM with cache, so 90% used with a green pressure graph is a healthy machine. Yellow means the system is compressing and evicting to keep up. Red means allocations are queuing behind memory that has to be freed first, which is what a spinning beachball actually is. From the terminal:
memory_pressure | tail -3 # system-wide free percentage
vm_stat 5 # page-ins/page-outs every 5s; watch pageouts climbSustained page-outs are the signal. A few gigabytes of swap sitting there is normal and costs nothing; swap being written continuously is what you feel.
On Linux, free -h plus the si/so columns of vmstat 5 tell you the same story. On Windows, the Performance tab’s “Committed” figure and the disk queue length in Resource Monitor are the equivalents.
Two loads dominate on developer machines and neither is mysterious. A browser with a few dozen tabs will hold gigabytes, because each renderer process is separate by design. A compile, a container build, or a running VM will saturate every core you have, which is fine and expected - that’s the CPU doing its job, not a fault.
The one that catches people out is a background scanner. Spotlight indexing a freshly cloned repo, or Windows Defender’s real-time protection scanning every file written into node_modules, produces exactly the symptom of a slow machine with no obvious culprit in the process list, because the cost is spread across thousands of tiny file operations. More on fixing that below.
Accumulation of unnecessary software
Development environments silt up faster than anything else on a computer. IDEs, SDKs, local databases, emulators, CLI packages, containers, and one-off utilities all accumulate, and a good number stop being used without ever being removed. They keep occupying disk, and some keep launch agents or helper daemons running at login. On a Mac, you can consider the CleanMyMac Mac optimization app for this purpose. It is designed to clean up unnecessary data, find large and redundant files, remove apps along with residual data, update software, and scan for potential threats. A dedicated Smart Care mode combines cleaning, security checks, storage management, software updates, and performance-related features. For developers, the value of such a tool lies primarily in centralized system maintenance. It can help uncover forgotten large files, duplicates, or apps that no one uses anymore.
The caveat is worth stating plainly: automatic cleanup shouldn’t replace knowing what’s on the machine. A directory named .venv, DerivedData, or pkg/mod looks like junk and isn’t necessarily. Local database volumes hold state you may not have backed up anywhere. Treat any cleanup pass as resource management with a rollback plan, not a race to free the most gigabytes.
Login items are the cheaper win and get ignored. Docker Desktop, Slack, an update daemon per language toolchain, and three helper agents from tools you uninstalled last year all start at boot. On macOS that’s System Settings > General > Login Items; on Windows, Task Manager’s Startup tab with its “Startup impact” column; on Linux, whatever your desktop calls autostart plus systemctl --user list-unit-files --state=enabled.
Storage and RAM management
Free space on an SSD is not just storage, it’s working room, and this is the single most under-appreciated cause of a machine that got slower over a year.
Two things happen as a drive fills. Most consumer SSDs run a dynamic SLC cache: they write incoming data one bit per cell, which is fast, then repack it into denser TLC or QLC cells later. That cache is carved out of free space, so it shrinks as the drive fills, and past about 90% there’s essentially none left. Every write then lands at the drive’s native, much slower speed. Second, NAND is written in pages but erased in much larger blocks, so reclaiming deleted space means shuffling still-valid data out of a block before erasing it. With plenty of free space the controller does that quietly while idle. With almost none, that housekeeping happens in the middle of your writes.
The practical rule from drive makers and reviewers alike: keep 20-25% of the system drive free, and treat anything under 10% as a performance problem rather than a storage one.
df -h / # macOS / Linux: how close to the edge are you
du -sh ~/Library/Caches/* 2>/dev/null | sort -h | tail -20ncdu is worth installing for this (brew install ncdu, apt install ncdu); it turns “where did 200GB go” into a few keystrokes.
On a developer machine, the usual answers are predictable and none of them are your project files:
| What | Where | Reclaim with |
|---|---|---|
| Docker images, volumes, build cache | Docker’s VM disk | docker system df, then docker system prune -a --volumes |
| Docker builder cache (counted separately) | Docker’s VM disk | docker builder prune |
| Xcode build products | ~/Library/Developer/Xcode/DerivedData | Safe to delete; Xcode rebuilds it |
| Old iOS simulators and device support | ~/Library/Developer/Xcode/ | xcrun simctl delete unavailable |
| Go module and build cache | ~/go/pkg/mod, ~/Library/Caches/go-build | go clean -modcache, go clean -cache |
| Gradle caches | ~/.gradle/caches | Delete transforms-* and build-cache-* |
| npm / pnpm stores | ~/.npm, pnpm store | npm cache clean --force, pnpm store prune |
| Rust registry and target dirs | ~/.cargo/registry, per-project target/ | cargo clean in projects you’ve finished |
| pip wheels | pip’s http cache | pip cache purge |
The Docker builder cache is the one people miss. It is not included in docker system prune without flags, and on a machine that rebuilds multi-stage images regularly it can reach tens of gigabytes on its own. Check docker system df before assuming it’s small.
Go through these periodically and delete only what you’re sure about. A cache rebuilds; a local database volume does not.
Memory during demanding tasks
With a browser, an IDE, Docker, a database, and a language server or two running at once, RAM is the first thing to run out. That doesn’t mean quitting every application before each build. It means finding what’s actually holding memory and capping it.
Some concrete places to look:
- Docker Desktop reserves a fixed slice of RAM whether or not containers need it. Its resource settings default high; on a 16GB machine, 8GB reserved for Docker is a real constraint on everything else.
- JVM tooling takes what you let it. The Gradle daemon, an Android build, and an IDE all size their heaps independently, and the defaults assume they’re the only thing running.
- Node builds on large codebases get capped by
NODE_OPTIONS=--max-old-space-size, which people raise to stop a crash and then forget. - Language servers are quietly expensive.
rust-analyzer,gopls, and TypeScript’s server each index your whole project, and having several editors open means several copies.
Then adjust how you work:
- Close environments you aren’t using
- Cut the number of services running at the same time
- Review how your tools are configured
On Linux, zram compresses swap in memory instead of writing to disk and is a genuinely good trade on a machine that’s short on RAM. earlyoom or systemd-oomd will kill the runaway process before the desktop locks up, which beats a hard reset. On Apple Silicon, memory and swap share the SSD, so heavy sustained swapping costs you write endurance as well as speed.
Monitoring heat, power, and system health

Image source: Depositphotos
Thermal limits are the reason a fast machine gets slow only during the work you care about. Benchmarks finish before heat builds; a twelve-minute compile does not. Sustained load is where cooling decides your actual clock speed.
Don’t guess at it. On macOS:
pmset -g thermlog # streams; prints CPU_Speed_Limit when it changes
sudo powermetrics --samplers smc # live SMC sensor data, including die temperaturepmset -g thermlog keeps running until you stop it with Ctrl-C, printing a line whenever the state changes. A CPU_Speed_Limit below 100 means the system is throttling. Start it, run your build, and watch.
On Linux, install lm-sensors and run sensors-detect once, then sensors gives you per-core temperatures. s-tui puts frequency, temperature, and power on one screen and makes throttling obvious as a frequency graph that steps down and stays there under load. On Windows, HWiNFO does the same job.
Temperatures climb during exactly the work that matters:
- Long compilation runs
- Rendering
- Working with containers
- Other sustained, resource-intensive tasks
Keep the ventilation openings clear, and don’t sit a laptop on something that blocks the air intake. Fan intakes on most laptops are underneath, which is the surface a bed, a sofa cushion, or a stack of paper covers completely. A hard surface or a stand fixes more thermal complaints than any software setting. Dust in the fins is the other half of it, and on a three-year-old machine it’s usually significant.
If the machine regularly overheats and loses performance at the same time, the cooling system is the problem. Look there rather than hunting for a way to “overclock” the processor in software.
Don’t ignore updates
Updates to the OS, IDE, drivers, and development tools usually carry bug fixes, stability improvements, and security patches. Installing them blind on release day is a different matter, especially for anything close to production.
For projects that matter, run a controlled update process:
- Check dependency compatibility first
- Keep known-good versions of your tools around
- Don’t change the whole environment at once
In practice that means pinning your toolchain the same way you pin dependencies. A version manager (mise, asdf, nvm, rustup) lets you install a new runtime alongside the current one and switch back in a second when something breaks, which is a much better position than discovering mid-sprint that a minor version bump changed a compiler default. Commit the lockfile. And stage the big ones: a major OS release in the first week is a fine choice for a spare machine and a poor one for the laptop your deadline depends on.
Optimizing the workflow itself

Image source: Depositphotos
Plenty of “slow computer” is slow process. If you’re doing the same things by hand every day, that’s where the time is going:
- Running the same commands
- Clearing temporary files
- Switching between a dozen tools
Scripts, aliases, automated tests, task runners, and a clear project structure buy back time without new hardware. The goal is automating what repeats, not building automation for its own sake.
Measure before and after, or you’re guessing:
time npm run build # one-off, good enough for big differences
hyperfine 'npm run build' # repeated runs, warmup, statisticshyperfine runs a command several times and reports mean and standard deviation, which matters because build times vary a lot run to run and a single 10% “improvement” is usually noise.
The changes that actually move those numbers, roughly in order of payoff:
- Exclude build directories from real-time antivirus scanning. On Windows this is frequently the largest single win available, because Defender inspects every file written during an install or build, and
node_modulesalone can be tens of thousands of files. Exclude specific build and dependency directories, never a whole drive:Add-MpPreference -ExclusionPath 'C:\src\myproject\node_modules'. Each exclusion is a real gap in coverage, so keep the list short and specific. The macOS equivalent is adding project and cache directories to System Settings > Spotlight > Search Privacy so the indexer stops walking them. - Use a compiler cache.
ccacheorsccachefor C and C++, Gradle’s build cache for JVM projects, and turbo or nx for JavaScript monorepos all turn a rebuild into a lookup when nothing relevant changed. - Stop doing full rebuilds. Watch modes and incremental compilation exist in almost every toolchain now. A full clean build should be something you do when you suspect the cache, not every time.
- Run the tests that matter. Test selection on changed files locally, full suite in CI, is a far better split than waiting on everything before every commit.
- Move the heaviest jobs off the laptop. A remote build machine or a dev container sidesteps both the thermal and the memory problem entirely for the work that triggers them.
It’s also worth timing things occasionally: how long a project actually takes to run, build, or get through its test suite. If the numbers haven’t moved but the machine “feels” slower, the cause is in a specific operation, and optimizing the whole computer won’t find it.
Conclusion
A fast workstation doesn’t need constant attention. It needs a routine:
- Keep 20% of the system drive free, and know which caches to clear when you don’t
- Read memory pressure rather than memory used, and cap the tools that over-allocate
- Confirm throttling with a thermal log before blaming the hardware
- Update deliberately, with a way back to the last working toolchain
- Keep scanners and indexers out of your build directories
- Measure builds with a timer instead of judging by feel
The part that matters most is not confusing optimization with cleaning for the sake of it. Every symptom here has a specific cause you can confirm in a minute or two, and confirming it first is what separates a fix from a weekend of reinstalling things. Find the cause, then change one thing.