You're staring at your feature. It works. The logic is clean. You've got a handful of hardcoded values: a page size, a retry count, a timeout. And now you're looking at next week and thinking, "I should probably build a config system before we ship this."
Stop.
Your instinct to ship is right. Your instinct to build infrastructure first is the voice in your head that delays good work. The hardcoded version is the version that ships.
I'm not saying hardcoding is always the answer. I'm saying the time to extract those values into a config layer is after you know the feature matters. Not before you've tested it with real people. Not before you've learned whether those retry counts actually need to change.
Hardcoded values that actually ship fine
A page size of 50. A max retry count of 5. A timeout of 30 seconds. A user ID limit during beta. These are business logic, not secrets. They should ship hardcoded.
The question is not "is this hardcoded?" The question is "does this value actually need to change without a code deploy?" For most first releases, the answer is no. You'll learn whether it needs to change after people use the feature. Then you'll change it.
Hardcode your pagination. Hardcode your rate limits. Hardcode your beta user list. Name them clearly so the next person reading your code knows they're intentional.
``` // page size per request, tuned for initial release const pageSize = 50;
// max retry attempts on transient failure const maxRetries = 5;
// timeout for external API calls, milliseconds const requestTimeoutMs = 30000;
// beta: limit access to specific users during rollout const betaUserIds = ['user_123', 'user_456', 'user_789']; ```
When you deploy this, the code is readable. The values are findable. The intent is clear. It ships.
Marking code for later without blocking release
Your feature works. It ships with hardcoded values. You know that pagination limit or that timeout might need tuning. You also know you're not waiting to tune it before you ship.
Leave yourself a marker. A comment, a ticket, a note in your deploy checklist. Not to stop the ship. To flag it as a refactor candidate once the feature is live and you have real data.
``` // TODO: extract to config once we see real usage patterns const pageSize = 50; ```
Or:
``` // TECH_DEBT: after 2 weeks in prod, check if this retry count is holding const maxRetries = 5; ```
Hardcoding is intentional, acceptable debt. Not the regrettable kind you should avoid. The marker tells the story: this was deliberate, and here's when to revisit it.
You ship anyway. Commit the code with the marker. When someone asks "why is this hardcoded?", the comment answers it. When the feature has been live and you need to tune it, the marker is already there waiting.
The real guardrails: what never gets hardcoded
Not every value should be hardcoded. There is a line.
Secrets: API keys, database credentials, tokens. These never stay hardcoded. They belong in environment variables from day one. The hardcoding rule stops at security.
Sensitive business logic: pricing, discount percentages, user roles or permissions, fraud thresholds. These can start as hardcoded for the first release. But they should move to a config system or database table before the feature goes to production at scale. If one mistake in a hardcoded value could cost money or violate compliance, it belongs in a system that can be changed by non-developers.
Third-party integrations: if your feature calls another service and needs an endpoint URL, API version, or webhook callback, hardcode it for the first release but plan to extract it quickly. These change with third-party updates and different environments.
Everything else is fair game for shipping hardcoded.
Why hardcoding beats building config systems
Here is the trap I see builders fall into: they ship a feature with hardcoded values, then spend the next week building a config system to replace those values. Then they spend months not changing any of the config values because the feature is working fine.
The config system was built to solve a problem that does not exist yet.
Hardcoding accelerates learning. You deploy with a page size of 50. Users tell you it's too small. You change it to 75 and redeploy. You learn in days what a config system would have hidden for weeks while you were building it.
A hardcoded list of beta users is the simplest form of access control. There is no platform needed. The next step up is a proper feature flag system, but that comes after you know this feature matters. Ship the hardcoded list first.
The moment you have a real, repeating need to change a value without redeploying, that is when you extract it. Not before. Not "just in case." When you actually need it.
Building a config layer because you might need to tune something is like building infrastructure for a problem you have not confirmed yet. Hardcode it. Ship it. Learn from it. Then extract if needed.
Extracting hardcoded values when it matters
When does extraction matter? When the value actually needs to change without a deploy, or when you've learned that it needs to change frequently.
A pagination limit that never changes? Leave it hardcoded. A retry count that adjusts based on load? Extract it to an environment variable or config service once usage patterns are clear and you need to tune it without redeploying.
Extracting hardcoded values ranks low on your refactoring priorities until the business need is real. You do not build infrastructure for a problem you have not confirmed yet.
The refactor is five minutes. You find the hardcoded value, move it to an environment variable or a config file, and redeploy. You've already shipped the feature. You've already learned whether this value matters. The extraction is not a delay. It's a small maintenance task you do once you need it.
This is why shipping the hardcoded version first is the move. You build infrastructure once the problem is real, the feature is live, and you know exactly what you need.
FAQ
Q: Should hardcoded values go into version control?
Yes. Commit them if they're non-sensitive and intentional. The goal is to ship and mark for refactoring later, not to hide the hardcoding from your team. A commit message like "ship with hardcoded page size, extract to config after we see usage" tells the next reader exactly what you did and why.
Q: What's the difference between a hardcoded value and a magic constant?
A hardcoded value is deliberate business logic (5 retries, 100 results per page) with clear naming and reasoning; a magic constant is the same thing without explanation. Good hardcoding documents itself.
Q: What if different environments need different hardcoded values?
Start by hardcoding the dev default. For staging and production, you can use separate configs or branches if the value is truly environment-specific, or keep it hardcoded and change it before each deploy if the value rarely shifts. You don't need a unified config system yet. Once you've confirmed that a value needs to change between environments repeatedly, that's your signal to extract it to environment variables. The extraction is still a five-minute refactor, and you're doing it because you've proven the need, not because you predicted it.