Taming Magic Numbers With A Constants Toolbox!

Wed, June 4, 2025 - 3 min read
A code comment explaining a constant

🧱 Taming Magic Numbers With A Constants Toolbox

On our project everything revolves around constants: story auto-close timers, retry limits, AB split ratios, compression thresholds. When those values hide across the codebase, we lose hours chasing “mysterious 42s”. A dedicated constants module gave us clarity and spared the team from accidental regressions.


Why magic numbers do not belong in production

📖 Context vanishes

Future maintainers cannot guess what 3000 means. Timeout? Character limit? Analytics sampling? A named constant like STORY_AUTO_CLOSE_TIMEOUT_MS answers instantly.

🔧 Changes turn into roulette

Updating a value means hunting every single occurrence. Miss one — and the feature breaks. A single entry inside constants.ts lets us change it once without collateral damage.

🧩 Cross-team alignment suffers

Frontend, backend, analytics, and marketing teams need the same numbers. Shared constants remove disagreements and simplify documentation.


Building a healthy constants module

// constants/timeouts.ts
export const STORY_AUTO_CLOSE_TIMEOUT_MS = 3000;
export const NOTIFICATION_HIDE_DELAY_MS = 5000;
export const ANALYTICS_BATCH_SIZE = 50;
  • Group by domain. timeouts, featureFlags, experiments, pricing — logical folders make navigation obvious.
  • Document everything. Reference decisions, tickets, or research. Remember our power of comments article and explain the “why”.
  • Export intentionally. Keep scope limited; if a value is internal, do not leak it beyond the module.
  • Sync with other layers. Align with backend and analytics on where shared constants live: monorepo package, shared-config, or environment service.

Common places magic numbers hide

  • 💳 Payments. Refund caps, fee percentages, conversion rates.
  • 📨 Notifications. Campaign frequency, push TTL, quiet hours.
  • 📦 Plans and subscriptions. Our catalog easily holds 20–30 packages; memorizing them is impossible. Named constants with prices and limits show instantly what each plan unlocks.
  • 🎬 UI/UX. Animation durations, virtual list heights, cards per viewport.
  • 🧪 Experiments. Traffic allocation, metric thresholds, experiment length.
  • ⚙️ Infrastructure. Retry strategies, queue limits, request timeouts.

Every one of these deserves a named constant and a comment.

Environments add another trap. Dev, stage, trunk, and production run on different base URLs, API keys, and sometimes separate event buses. Constants like DEV_API_BASE_URL, STAGE_CUSTOMER_PORTAL_URL, and PROD_SUBSCRIPTION_PACKAGE_IDS prevent misconfigured builds. When the base API changes, you update a single entry and keep the whole crew aligned.


Helping the team embrace constants

  1. Expose the pain. Collect post-mortems where “search and replace” failed. Share PRs that introduced regressions because numbers lived everywhere.
  2. Propose a skeleton. Raise a PR with the initial constants module and an eslint rule like no-magic-numbers to catch offenders.
  3. Automate access. Configure import aliases so grabbing constants is frictionless, and wire CI checks to enforce the pattern.
  4. Write the playbook. Document which values move immediately and which wait for product approval.

Takeaway

Magic numbers are silent debt. Create a central constants file, annotate every value, and link the source of truth. The next engineer will know why the timer sits at 3 seconds, product will trust the coefficients, and releases will stay predictable. Less magic, more clarity, happier team.