Why Is My Coffee Cold aka Optimizing Build Performance in Visual Studio
If your Visual Studio builds are slower than a Monday morning stand-up, this expert guide dives into project structure, MSBuild tricks, NuGet bottlenecks, and parallelism to help you optimize builds, reclaim flow, and stop staring at loading bars.
You know the drill. You hit Build Solution (Ctrl+Shift+B), stand up to make a cup of coffee, come back… and Visual Studio is still "thinking." Not debugging. Not compiling. Just sitting there, spinning up MSBuild like it's trying to warm the server room through friction alone.
If you've ever stared into the blinking abyss of the build output window and wondered whether it's worth switching careers—this article is for you.
Let’s take a deep dive into the performance rabbit hole. Not the shallow stuff like “buy an SSD”—you’ve already done that. I’m talking project structures, parallel builds, SDK-style vs legacy formats, NuGet black holes, and that wonderful beast called Incremental Builds.
🧱 The Fundamentals: It’s All MSBuild, All the Way Down
Visual Studio's build system is powered by MSBuild—essentially a glorified XML orchestrator from 2005 that’s been through more updates than your average iPhone.
MSBuild takes your .csproj
and .sln
files, resolves dependencies, compiles code, runs pre/post build events, and eventually emits binaries (or complaints). The faster it can do this, the sooner you can get back to writing code—or at least pretending to.
Key factors affecting performance:
- Project structure & number of references
- Target framework(s)
- NuGet package resolution
- Disk I/O and file system watchers
- Visual Studio extensions (yes, even that cute one with the cat)
🔍 Diagnosis Before Cure: Measuring Build Times
Before you change anything, know what’s slow.
Tools to Diagnose:
- MSBuild Binary Logger (
/bl
)
Capture detailed logs with:
msbuild YourSolution.sln /bl
Analyze with MSBuild Structured Log Viewer- Project System Tools (Visual Studio extension)
Logs real-time solution load and build performance. - Diagnostic Build Output
Turn on "Detailed" or "Diagnostic" in Tools > Options > Projects and Solutions > Build and Run.
You can't optimize what you can't measure. Trust the logs.
🧬 SDK-Style Projects: Evolution Has Benefits
SDK-style projects (used in .NET Core and .NET 5+) are not just cleaner—they're faster to load, resolve, and build.
Benefits:
- Fewer imports and targets → faster evaluation
- Simplified
csproj
structure → fewer parsing steps - Auto-inclusion of
Compile
,EmbeddedResource
, etc. → no globbing delay
Legacy .csproj
files (think pre-2017) are like having an old flip phone in a 5G world. Upgrade, or be left behind in the dust of incremental builds that never were.
⚙️ Parallel Builds & Multi-Core CPU Party Tricks
Visual Studio has been parallelizing builds since... well, before TikTok was a thing.
How to enable it:
- Tools > Options > Projects and Solutions > Build and Run
- Set maximum number of parallel project builds to match your logical cores (minus a few if you’re running VMs or Docker in the background)
Also add this in Directory.Build.props
:
<Project>
<PropertyGroup>
<BuildInParallel>true</BuildInParallel>
</PropertyGroup>
</Project>
🚨 Caveat: Parallel builds can trip over:
- Cross-project dependencies
- Shared output folders (bad practice anyway)
- Race conditions in custom build scripts
🪝 Incremental Builds: Your Compiler's Lazy Mode
MSBuild checks timestamps to determine what needs to be rebuilt. If you touch a file unnecessarily, you might accidentally trigger a full rebuild.
Tips to keep builds incremental:
- Avoid custom targets that “touch” outputs on every run
- Don’t output to the same folder from multiple projects
- Use
Inputs
andOutputs
metadata properly in your custom tasks - Store
.pdb
and intermediate files in unique folders per configuration/platform
Enable diagnostic logs and look for entries like:
Skipping target "CoreCompile" because all output files are up-to-date.
If you never see that message—you’re paying a performance tax every time you hit Build.
📦 NuGet: The Slow Thief in the Night
If your build feels like it's waiting for a conference call with NuGet.org—you're not wrong.
Optimization Techniques:
- Use PackageReference instead of
packages.config
- Turn on Restore on Build only when necessary
- Enable deterministic restore in CI/CD
- Add a local NuGet cache/server (like
nuget.server
, Artifactory, or GitHub Packages)
And for heaven’s sake, don’t commit packages
folder into Git. That’s 2013’s problem.
🧼 Clean Architecture ≠ Clean Builds
It’s tempting to hit Rebuild
when something’s weird. But if you’re rebuilding 80 projects because one model class changed, that’s not “clean”—that’s a dependency hygiene issue.
Things to try:
- Break large monolith solutions into smaller, domain-specific solutions
- Use project references sparingly
- Consider NuGetizing shared libraries to decouple their build cycle
Good architecture reduces compile-time dependencies. This is the build-time manifestation of SOLID principles—especially Dependency Inversion.
🧪 CI/CD Considerations: Don’t Ship Your Laptop's Problems
In CI, the rules change. You want:
- Repeatable builds (use
dotnet build --no-restore
) - Isolated environments (no leftover global state)
- Binary logging to catch anomalies post-mortem
- Caching (
~/.nuget
,obj
,bin
) where possible
Dockerized CI builds can be a huge help here. And yes, build containers are faster once warmed up, because they’re predictable and slim.
🔌 Extensions: The Silent Saboteurs
That whimsical extension that colorizes your curly braces? It might be causing delays during project load or solution reload.
Audit your extensions:
- Use
devenv /safemode
to test performance without them - Disable anything not essential for builds
Less is more. Especially when you're juggling 200k lines of code and 40+ projects.
📁 File Watchers, Antivirus, and External Interference
Your build performance is often at the mercy of things outside Visual Studio.
Recommendations:
- Exclude
bin/
,obj/
, and.vs/
from real-time antivirus scanning - Disable aggressive file system watchers (OneDrive, Dropbox, etc.)
- Ensure your project is on an SSD or NVMe drive
- Avoid network-mounted drives for source code
Visual Studio is fast—if you let it be.
⛩️ Directory.Build.props and the Power of Centralization
Want to streamline settings across a solution? Use Directory.Build.props
and Directory.Build.targets
.
Examples:
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
</PropertyGroup>
</Project>
This helps:
- Reduce per-project duplication
- Avoid analyzer overhead during builds
- Enforce consistency
Think of it as your solution-wide config file—like web.config
, but for grownups.
🧠 The Mental Model: Think Like a Compiler
Every optimization should stem from this question:
What would I do if I had to rebuild this solution from scratch?
Would you recompile the logging library every time? Would you refetch the same packages? Would you rebuild 40 projects when only one changed?
Probably not. MSBuild agrees—with a little guidance.
🏁 The Finish Line: Build Once, Ship Fast
Build performance isn't just about speed—it's about developer flow. Every second wasted staring at a progress bar is a second not spent solving real problems.
So invest in it. Understand it. Profile it. And for the love of debugging, stop using "Rebuild Solution" as a panic button.
🎯 TL;DR
- Use SDK-style projects and PackageReference
- Parallelize builds and reduce dependencies
- Profile with MSBuild binary logs
- Optimize incremental builds
- Beware NuGet, antivirus, and rogue extensions
- Use Directory.Build.props for consistency
- Clean architecture = cleaner builds
🧩 In Conclusion...
🧘 Fast builds aren’t an accident—they’re architecture, discipline, and a touch of obsession. Build smarter, not harder.