Quick answer: Singletons are convenient global access points that quietly become a tangle of hidden dependencies, making code hard to test, reason about, and change. Limit them sharply, prefer passing dependencies explicitly, and treat the urge to add 'just one more manager' as a warning sign.
Singletons—globally accessible single instances—are one of the first patterns game developers reach for, because they make it trivial to access shared systems from anywhere. They're also one of the most overused patterns, and a codebase that grows a forest of singleton managers becomes a tangle of hidden dependencies that's painful to test, reason about, and change.
Convenience now, hidden coupling later
The appeal of a singleton is obvious: need the audio system from somewhere deep in your code? Just reference the global. No passing things around, no wiring, instant access. The cost is invisible at first and crushing later: every place that reaches for a singleton creates a hidden dependency that doesn't show up in interfaces, so you can't tell what a piece of code actually depends on without reading every line. This makes code impossible to test in isolation, hard to reason about because anything can touch anything, and dangerous to change because the dependencies are everywhere and unstated. A codebase where everything reaches into a dozen global managers has no real structure—just a web of implicit connections that grows more tangled with every 'just access the manager' shortcut.
The discipline is to treat the urge for a new singleton as a warning and prefer explicit dependencies. When you find yourself wanting to add another global manager, pause and ask whether the thing that needs access could instead be handed what it needs explicitly—passed in when it's created, so its dependencies are visible and it can be tested and reused. Explicit dependencies are more typing up front, but they make the structure of the code honest: you can see what depends on what, test components in isolation, and change things with confidence about what's affected. A small number of genuinely global, truly singular systems may justify the pattern, but most 'managers' don't, and the difference between a maintainable game codebase and an untouchable one is often just how disciplined the developer was about resisting the easy global. Limit singletons sharply, pass dependencies explicitly, and your future self will be able to understand and change the code instead of being trapped by it.
Polish where players actually look
Polish is not evenly valuable. Players form an impression in the first minutes and spend most of their time in the core loop, so effort spent there returns far more than effort spread thin across content few people reach. The opening, the moment-to-moment feel, and the things every player touches are where polish converts directly into how good the game feels.
Be deliberate about it. Make the first impression strong and the core interactions satisfying before widening out, because a great core with less content almost always beats a sprawling game that never feels good to play.
Scope is a decision, not an accident
Almost every overscoped game got that way one reasonable addition at a time, with no single decision ever feeling like the mistake. The finish line recedes a little with each new feature, and because the project always feels nearly done, the developer rarely notices how far the goal has drifted until they're exhausted and the game still isn't out.
Treat scope as something you actively decide rather than something that happens to you. Write down what the finished game contains, make every addition a conscious trade against that, and keep most new ideas in a backlog where they belong — because a small game you finish beats a large one you abandon.
Measure before you optimise
Intuition about what's slow, what's confusing, or what's driving players away is usually wrong, and acting on it wastes effort on problems that don't matter while the real ones persist. The developers who improve their games efficiently are the ones who measure first — profiling performance, watching real sessions, capturing actual errors — and let the data set their priorities.
It's slower than trusting your gut, but it's the only approach that reliably improves the game instead of just changing it. Find the biggest real problem, fix that, and measure again, rather than optimising guesses.
The first impression is most of the battle
More players leave in the opening minutes than at any other point, which makes the first few minutes the highest-leverage stretch of the whole game — and also the part the developer can least see clearly, having played it a thousand times. What feels obvious to you is often confusing to someone seeing it fresh, and that gap quietly costs you players before they ever reach the good part.
Get the player into the interesting part fast, let them feel competent quickly, and watch first-time players go through the opening without helping them. Nobody quits a game they're enjoying, so making the early minutes land is most of the battle for retention.
Small and finished beats big and abandoned
A folder of impressive unfinished projects teaches far less than a single small finished one, because finishing is where the hardest and most valuable lessons live — the unglamorous final stretch of bug-fixing, polishing, and shipping that ambitious abandoned projects never reach. Each completed game, however modest, builds the finishing muscle and the confidence that make the next one achievable.
So resist the pull of the dream project until you've shipped a few small ones. Scope to what you can actually complete, finish it, and let the experience of shipping make your bigger ambitions realistic.
Singletons trade convenience now for hidden coupling later. Pass dependencies explicitly and resist 'one more manager.'