The Term Hard Coding Refers To

8 min read

Hard coding. But you've seen it. You've probably done it. Maybe you're doing it right now.

That API key sitting in your config file? Hard coded. The file path that only works on your machine? Hard coded. Consider this: the magic number 3600 instead of SECONDS_PER_HOUR? Yeah. That too.

It's one of those things every developer learns early, nods along to, and then quietly ignores when deadlines loom. But understanding what hard coding actually means — and more importantly, when it bites you — separates code that survives from code that becomes a nightmare to maintain Not complicated — just consistent..

Let's talk about what it really is, why it keeps happening, and how to stop it from wrecking your project.

What Is Hard Coding

At its simplest, hard coding means embedding values directly into your source code instead of pulling them from somewhere configurable — environment variables, config files, databases, command-line arguments, you name it And it works..

# Hard coded
api_url = "https://api.stripe.com/v1"
timeout = 30
max_retries = 3

# Not hard coded
api_url = os.getenv("STRIPE_API_URL")
timeout = int(os.getenv("REQUEST_TIMEOUT", "30"))
max_retries = int(os.getenv("MAX_RETRIES", "3"))

The difference isn't syntax. It's where the value lives That's the part that actually makes a difference..

Hard coded values are baked into the binary, the container image, the deployed artifact. Plus, you redeploy. Even so, you rebuild. Change them? You pray the CI pipeline doesn't flake.

It's Not Just Strings and Numbers

People think hard coding is about literal values. It's broader than that.

  • File paths/home/user/data/input.csv works great until it doesn't
  • URLs and endpoints — staging vs production vs that one weird legacy service
  • Credentials — API keys, database passwords, JWT secrets (please don't)
  • Business logic thresholds — "free tier gets 100 requests" written as if count > 100
  • Feature flagsif user.id == 42: enable_new_dashboard() (yes, I've seen this)
  • Localization strings — "Welcome!" hardcoded in English everywhere
  • Database schema namesSELECT * FROM prod_users.orders in your query builder

Any value that might change across environments, deployments, customers, or time — and lives in your code — is hard coded.

The Spectrum of "Hard"

Not all hard coding is equal.

Level Example Pain When Changing
Literal port = 8080 Rebuild + redeploy
Constant DEFAULT_PORT = 8080 Rebuild + redeploy (but searchable)
Config file (committed) config.yaml in repo Rebuild + redeploy, but readable
Environment variable os.getenv("PORT") Restart only
External config service Consul, etcd, Parameter Store Instant, no restart

People argue about this. Here's where I land on it And that's really what it comes down to..

The further right you go, the more flexible. The further left, the more rigid.

Why It Matters

You might think: It works on my machine. I'll fix it later.

Here's what "later" looks like Easy to understand, harder to ignore..

Environment Drift

Your local machine runs PostgreSQL on port 5432. That's why cI runs it on 5433. Still, staging uses a managed instance with a different host. Production? Read replicas, connection pooling, TLS required Simple as that..

Hard code the connection string once, and you've just signed up for maintaining four slightly different branches — or worse, one branch with commented-out blocks like:

# LOCAL
# db_url = "postgres://localhost:5432/myapp"

# CI
# db_url = "postgres://ci-db:5433/myapp"

# STAGING
db_url = "postgres://staging-db.internal:5432/myapp?sslmode=require"

# PROD - DON'T TOUCH
# db_url = "postgres://prod-primary:5432/myapp?sslmode=verify-full"

I've seen this in production codebases at Series B startups. It's not rare.

Security Incidents Waiting to Happen

Hard coded secrets are the number one way credentials leak And that's really what it comes down to..

  • Developer commits .env file? Leaked.
  • Docker image builds with ARG API_KEY=...? Leaked in layer history.
  • Code snippet shared in Slack? Leaked.
  • Repository goes public by accident? Everything leaked.

Rotating a hard coded secret means finding every occurrence, updating, rebuilding, redeploying — across every service that uses it. Meanwhile, the old key is valid until you finish.

The "Just One More Deploy" Trap

Business wants to change the free trial from 14 days to 30. On the flip side, marketing wants to swap the homepage hero text. Legal needs the privacy policy URL updated And that's really what it comes down to..

If any of those are hard coded, each change is a deploy. A deploy means:

  • CI pipeline runs (10–30 minutes)
  • Code review (if you're lucky)
  • Staging verification
  • Production rollout
  • Rollback plan if something breaks

A text change becomes a half-day event. Multiply by ten changes a month. That's not engineering — that's bureaucracy.

Testing Becomes a Lie

Unit tests that depend on hard coded values test those values, not your logic Worth keeping that in mind..

def test_discount_calculation():
    # This test passes because 100 * 0.1 == 10
    # But what if the discount rate changes?
    assert calculate_discount(100) == 10

Now imagine that discount rate lives in a config. Your test should inject the rate. If it's hard coded, you're testing the constant, not the function.

Integration tests suffer worse. Hard coded endpoints mean you can't spin up a test double. You hit real services. Tests become flaky, slow, and expensive Most people skip this — try not to..

How It Works (And How to Stop Doing It)

The fix isn't "never write a literal value." It's knowing when a value belongs outside the code.

Step 1: Identify What Varies

Ask yourself: Could this value differ...

  • Between my laptop and CI?
  • Between staging and production?
  • Between Customer A and Customer B?
  • Next month when marketing changes their mind?
  • When we rotate credentials (which we should do regularly)?
  • When we migrate databases / cloud providers / APIs?

If the answer is yes to any of those, it doesn't belong in code.

Step 2: Choose the Right Abstraction

Not everything needs a distributed config system. Match the tool to the problem.

Environment Variables — The Baseline

# .env (local, never committed)
DATABASE_URL=postgres://localhost:5432/myapp
STRIPE_SECRET_KEY=sk_test_...
LOG_LEVEL=debug
# config.py
import os
from functools import lru_cache

@lru_cache
def get_settings():
    return {
        "database_url": os.Think about it: getenv("DATABASE_URL"),
        "stripe_secret_key": os. getenv("STRIPE_SECRET_KEY"),
        "log_level": os.

Simple, works everywhere, supported by every platform (Docker, Kubernetes, Heroku, Vercel, Lambda, etc.).

**Caveat:** Environment variables are strings. You handle parsing, validation, defaults. They're process-global — changing them requires a restart. Fine for most apps.

#### Config Files — For Structure

When you have nested configuration, multiple sections, or non-string types:

```yaml
# config.yaml (committed, template only)
database:
  host: "${DB_HOST

`.env.example` instead — a template with placeholder values that new developers copy and fill in.

```yaml
# config.yaml (committed, template only)
database:
  host: "${DB_HOST}"
  port: "${DB_PORT:-5432}"
  name: "${DB_NAME}"
  ssl: "${DB_SSL:-true}"

stripe:
  secret_key: "${STRIPE_SECRET_KEY}"
  webhook_secret: "${STRIPE_WEBHOOK_SECRET}"

features:
  new_checkout: "${FEATURE_NEW_CHECKOUT:-false}"
  maintenance_mode: "${MAINTENANCE_MODE:-false}"

Load it with a library like pydantic, dynaconf, or python-dotenv that handles type coercion, defaults, and nested structure for you Nothing fancy..

Secrets Managers — For Sensitive Values

API keys, database passwords, certificates — these should never appear in config files, even in .env. Use a secrets manager:

  • AWS Secrets Manager or Parameter Store
  • HashiCorp Vault
  • GCP Secret Manager
  • Azure Key Vault
import boto3
import json

def get_secret(name: str) -> dict:
    client = boto3.Also, client("secretsmanager")
    response = client. get_secret_value(SecretId=name)
    return json.

db_creds = get_secret("prod/database/credentials")

This means your app has zero static secrets. Rotation, auditing, and access control are handled by the platform. If someone leaves the team, you rotate the secret in one place — no code changes, no deploys Easy to understand, harder to ignore..

Feature Flags — For Runtime Behavior

Some values aren't infrastructure — they're business logic toggles:

def get_pricing(user: User) -> float:
    if feature_flags.is_enabled("new_pricing_model", user.tenant_id):
        return calculate_tiered_pricing(user)
    return calculate_flat_pricing(user)

Tools like LaunchDarkly, Unleash, or even a simple database-backed flag table let you change behavior without deploying code. This is especially powerful for:

  • Gradual rollouts (canary releases)
  • A/B testing pricing or UX
  • Killing a feature instantly when it breaks

Step 3: Validate at Startup

The worst thing that can happen is your app starts with missing or invalid configuration and fails silently mid-request. Fail fast:

from pydantic import BaseSettings, ValidationError

class Settings(BaseSettings):
    database_url: str
    stripe_secret_key: str
    log_level: str = "info"

    class Config:
        env_file = ".env"

try:
    settings = Settings()
except ValidationError as e:
    print(f"Configuration error: {e}")
    raise SystemExit(1)

If DATABASE_URL is missing or malformed, the app refuses to start with a clear error message. No cryptic connection timeouts at 3 AM.

Step 4: Document What's External

A config file with no documentation is a guessing game. Add a CONFIGURATION.md or use inline comments in your schema:

# config.yaml
# --- Database ---
# Host for the primary PostgreSQL instance.
# Defaults to localhost in dev; set DB_HOST in production.
database:
  host: "${DB_HOST}"

# --- Stripe ---
# Live secret key for production. Test key for staging.
# Never commit actual values — use your secrets manager.
stripe:
  secret_key: "${STRIPE_SECRET_KEY}"

When onboarding a new engineer, they should be able to look at this doc, know what they need to set up locally, and understand why each value is externalized Easy to understand, harder to ignore..


The Principle, Not the Pattern

Environment variables, config files, secrets managers, and feature flags are all **means

to an end** — the end being separation of concerns between code and configuration.

The principle isn't "use environment variables" or "put everything in AWS Secrets Manager." It's about making your application's behavior explicitly dependent on external inputs that can be changed without touching the code itself.

This gives you:

  • Portability: The same code runs in dev, staging, and prod
  • Security: Sensitive values never live in source control
  • Flexibility: You can change behavior without deployments
  • Observability: You know exactly what configuration your running app is using

Whether that's through os.getenv(), a YAML file, a secrets manager API call, or a feature flag service — the mechanism matters less than the discipline.


Conclusion

Configuration management isn't a deployment detail — it's a foundational practice that determines how safely and efficiently your application evolves. By treating configuration as a first-class concern, you eliminate entire classes of bugs, reduce deployment risk, and make your system operable by humans who weren't there when it was built Which is the point..

Start small: pick one source of truth for your environment-specific values, fail fast on missing config, and document what each setting does. The compound effect of these practices will pay dividends in every deployment, every incident response, and every new team member who joins your project.

Your future self — debugging at 3 AM — will thank you.

New Additions

Fresh Stories

More in This Space

From the Same World

Thank you for reading about The Term Hard Coding Refers To. We hope the information has been useful. Feel free to contact us if you have any questions. See you next time — don't forget to bookmark!
⌂ Back to Home