Magic in Programming — What It Is, the Dangers of Magic Numbers and How to Replace Them

Author: IT Sectr Published: 2026-07-27 Reading time: 10 min

Magic in programming is not a metaphor but a precise term denoting values (numbers, strings, flags) whose meaning is not obvious from context and requires external knowledge to understand. The most common type of magic is magic numbers: numeric constants written directly into code with no explanation of why that particular value was chosen. According to the SonarSource Code Quality Report (2025), about 8 percent of all static analyzer warnings are related to unexplained literals. Magic values make code brittle: changing them requires finding all occurrences, and a new developer cannot tell whether a number can be modified or if it is critical for the system to work.

Key Takeaways

  • Magic — implicit numbers, strings, and flags in code whose meaning is hidden from the reader.
  • Magic numbers — numeric literals without names: 86400, 3.14, 0.85, 1024.
  • Magic strings — hardcoded paths, keys, URLs without extracting them into constants.
  • Detection tools: SonarQube (MagicNumber rule), ESLint (no-magic-numbers), Detekt.
  • Solution: extract every magic value into a named constant with a descriptive name.

What Is Magic in Programming?

Magic is any value in source code whose meaning is not obvious without additional domain knowledge. The term is established in the community: if a developer looks at a number and cannot tell where it came from — that is magic.

Magic comes in several types: numeric (magic numbers), string (magic strings), boolean (magic flags), and configuration (hardcoded parameters that should be in settings). All four types share one problem: when a requirement changes, the developer must find every place the value is used and replace them manually. Missing even one occurrence leads to a bug.

According to the JetBrains Code Quality Survey (2025), 73 percent of developers consider magic numbers an indicator of low code quality, while 41 percent admit they occasionally leave them in. The main reason is rushing: “I’ll add the constant later” — but later never comes, and a month later the number 0.85 remains in the middle of a method body with no explanation.

The key rule: every literal value except 0, 1, true, false, and empty string should be extracted into a named constant. Exceptions: counter increment (i + 1), mathematical zeros (checking for 0), and initial accumulator values. Everything else is a candidate for naming.

Magic Numbers and Why They Are Dangerous

A magic number is a numeric literal whose value is not obvious from context. A classic example: 86400 in timeout-related code. A developer sees the number and must guess that it is the number of seconds in a day. If they make a mistake and write 84600, the bug will be hard to catch because the timeout will fire 18 minutes early.

Why magic numbers are dangerous: first, they harm readability. The number 1024 could mean a kilobyte size, a pagination threshold, or a maximum number of items. Without context — it is just a number. Second, they create duplication: if 1024 is used in five places, when the threshold changes to 2048, the developer must find all five and replace them. If one place is missed, the system works incorrectly but without an explicit error.

Example of magic numbers before and after

kotlin
// before - magic in its pure form
fun calculateTimeout(base: Int): Int {
    return base * 3 + 5000
}

// after - values replaced with constants
private const val RETRY_MULTIPLIER = 3
private const val BASE_TIMEOUT_MS = 5000

fun calculateTimeout(base: Int): Int {
    return base * RETRY_MULTIPLIER + BASE_TIMEOUT_MS
}

The third danger is inability to test. If a threshold value is hardcoded as a literal, the test cannot override it to verify boundary conditions. A constant extracted to a companion object or configuration file makes the code testable: the test substitutes a different value and checks system behavior at the boundary.

Develop a habit: every time you write a number other than 0, 1, 100, or 2 — stop and consider whether it should be extracted into a constant. If the number is related to business logic (limit, threshold, timeout, size) — extract it without hesitation. If the number is a mathematical constant (pi, e) — use the standard library (Math.PI, Math.E).

Magic Strings and Paths

Magic strings are string literals embedded in code without being extracted into constants or resources. Typical examples: endpoint URLs, SharedPreferences key names, Intent Actions, bundle keys, file names, and SQL queries.

The danger of magic strings is the lack of compile-time checking. A typo in the string “user_prefs” will not be caught until runtime. If the string is used in ten places and the developer writes “user_pref” (missing the s) in one of them — the app does not crash, but data is not saved. Such a bug can live in production for months because it does not cause a crash.

For Android projects, magic strings should be extracted into resources (strings.xml, arrays.xml) or constants in a companion object. For iOS — into string resources (Localizable.strings) or enum constants. For backend — into configuration files (.env, application.properties). No key, URL, or path should appear in code as a string literal.

swift
// before - magic strings across the class
let prefs = UserDefaults.standard
prefs.set(token, forKey: "auth_token")
prefs.set(userId, forKey: "current_user_id")

// after - strings extracted to enum
enum PrefKeys: String {
    case authToken = "auth_token"
    case currentUserId = "current_user_id"
}

prefs.set(token, forKey: PrefKeys.authToken.rawValue)
prefs.set(userId, forKey: PrefKeys.currentUserId.rawValue)

Pay special attention to strings that are duplicated. If the same key “user_settings” appears in three files — 99 percent of the time a typo will eventually appear in one of them. Extracting into an enum or constant guarantees that all references use the same value.

Magic Flags and Boolean Parameters

Magic flags are boolean parameters whose meaning is not obvious from the call context. A classic anti-pattern: passing true or false to a method with no explanation of what exactly that flag enables or disables.

Example: userDao.fetch(includeDeleted = false). A developer sees false and cannot tell whether it means “don’t include deleted” or “don’t include active.” A month later, false turns into true, and deleted records start appearing in the output. The bug is only discovered in production.

The solution is to replace boolean flags with an enum or sealed class. Instead of a Boolean parameter, use UserFilter.includeDeleted or UserFilter.activeOnly. This way the code documents its intent, and the IDE suggests available options during autocomplete.

If a boolean flag is passed through multiple layers — that is another signal that the abstraction is wrong. Instead of dragging a flag through three levels of calls, consider whether the filter choice should be made at the top level and passed as a ready-made configuration. The fewer boolean flags in the code — the less magic.

Adopt a rule: no boolean parameter is passed to a method without a named argument (if the language supports named arguments). In Kotlin and Swift, this requirement is automatic. In Java, use Builder or enum constants instead of true/false.

Magic Detection Tools

Finding magic values is automated by static analyzers configured to detect literals in unexpected places. Each language offers its own tools with customizable exceptions.

ToolLanguagesRule
SonarQubeJava, Kotlin, Swift, Python, JSMagicNumber, HardcodedString
ESLintJavaScript, TypeScriptno-magic-numbers, no-hardcoded-strings
DetektKotlinMagicNumber, ComplexCondition
SwiftLintSwiftmagic_number (opt-in)
PMDJava, Apex, PLSQLMagicNumber (configurable allowed list)
PhpStorm InspectionsPHPNumericLiteralWithContext (built-in inspection)

Configuring exceptions is critical — without it, the analyzer will flag every increment (-1, +1) and mathematical zero. For SonarQube, the allowed number list: 0, 1, -1, 2 (for doubling), 100 (percentages), 60 and 24 (time). For all other values — require a named constant with public static final (Java) or const val (Kotlin) modifier.

For CI-level analysis, add a step that checks for magic as a warning but does not block the build. The first run will show hundreds of warnings in legacy code. Gradually, ticket by ticket, migrate the code to constants and raise the quality threshold. When the number of magic numbers falls below 10 — enable the rule as a build error.

Refactoring: Replacing Magic with Constants

Refactoring magic is one of the safest operations: replacing a literal with a constant does not change code behavior. Nevertheless, the approach must be systematic to avoid missing hidden dependencies (for example, if the same magic number is used in unrelated contexts but happens to have the same value).

Step-by-step process: find all occurrences of the magic value, understand the context of each, split into different constants (even if the values match — the contexts are different, and constants should be named differently), replace literals with constants, verify through tests. The mistake in step 2 is the most common: two different concepts (a timeout in milliseconds and a threshold in bytes) may numerically coincide (for example, 5000), but semantically they are different quantities and cannot be combined into one constant.

java
// before - same number in different contexts
public class Config {
    public void setupCache() {
        cache.setMaxSize(5000); // 5 MB
    }
    public void setupTimeout() {
        client.setReadTimeout(5000); // 5 seconds
    }
}

// after - different constants for different contexts
public class Config {
    private static final int CACHE_MAX_SIZE_MB = 5;
    private static final int READ_TIMEOUT_SECONDS = 5;

    public void setupCache() {
        cache.setMaxSize(CACHE_MAX_SIZE_MB * 1024 * 1024);
    }
    public void setupTimeout() {
        client.setReadTimeout(
            READ_TIMEOUT_SECONDS * 1000
        );
    }
}

For new code, the rule is simple: any literal except 0, 1, -1, true, false, null, and empty string is extracted into a constant. Exceptions: mathematical constants (always use the standard library), test data (literals can remain in tests but with a descriptive variable name), and boundary values for increment (i + 1 in a loop is fine).

Frequently Asked Questions

Is 100 a magic number if it means 100 percent?

Yes, 100 is also a magic number if used without context. Instead of 100, write MAX_PERCENT or PROBABILITY_SCALE. Exception: when 100 is obviously a percentage in context (for example, in a percentage calculation formula), but even in this case a constant improves readability.

What about numbers in tests?

In tests, it is also better to use named variables. Instead of assertEquals(42, result), write val expected = 42; assertEquals(expected, result). Exception: tests for boundary values (0, null, empty string) — they can remain as literals because they are readable in the test context.

Should numbers be extracted into Android resources?

Yes, numbers related to UI (sizes, margins, animation duration) should be in resources (dimens.xml, integers.xml). Business constants (timeouts, limits) — in a companion object or configuration file. The main criterion: if a number can change without changing the logic — it is a resource.

How to find magic numbers in a legacy project?

Run SonarQube with the MagicNumber rule or ESLint with no-magic-numbers. Get the report, sort by usage frequency, and start with numbers that appear in three or more places. They are the most likely candidates for extraction into constants.

Should every number in code be extracted into a constant?

No. Acceptable literals: 0, 1, -1 (increment/decrement, empty check), true, false, null, empty string. All others require naming. If the number 0 is used not as an empty check (for example, 0 is the root category ID), then 0 should also be a constant: ROOT_CATEGORY_ID = 0.

Summary

  • Magic — literals without explanation: numbers, strings, flags whose meaning is hidden from the code reader.
  • Magic numbers — unnamed numeric constants (86400, 1024, 0.85, 5000) requiring domain knowledge to understand.
  • Magic strings — hardcoded keys, URLs, and paths invisible to the compiler, leading to runtime bugs.
  • Magic flags — boolean parameters whose value is not obvious (true/false in a method call).
  • Tools: SonarQube, ESLint, Detekt, SwiftLint, PMD — all support the MagicNumber rule.
  • Solution: every literal (except 0, ±1, true, false, null, "") is extracted into a named constant with a descriptive name.
  • Different contexts — different constants: 5000 as a timeout and 5000 as a cache size are different entities.

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also