The Underscore You’ve Been Ignoring: Unnamed Variables & Patterns

~
~
Published on
Authors
java-22-banner

The Underscore You’ve Been Ignoring: Unnamed Variables & Patterns

Junior devs ask: “Why do people keep writing _?”
Senior devs smirk: “Because not every value deserves a name.”

Modern languages let you accept a value while signaling you don’t care about it. That’s what unnamed variables, discards, and unnamed patterns are for. They improve readability, guide compilers and linters, and reduce accidental coupling.

TL;DR: Use _ when a value is required by syntax but irrelevant to your logic—especially with pattern matching and deconstruction (hello, Java record patterns).

Why it matters

  • Intent: Readers instantly know a value is intentionally ignored.
  • Noise reduction: Fewer meaningless names like unused1.
  • Compiler help: Enables exhaustiveness checks and warnings without fake bindings.
  • Refactor safety: Changing the ignored part won’t ripple through the code.

The quick tour (by language)

Java (modern pattern matching)

record Point(int x, int y) {}

static String quadrant(Object o) {
  return switch (o) {
    case Point(_, int y) when y > 0 -> "Top";
    case Point(_, int y) when y < 0 -> "Bottom";
    default -> "Unknown";
  };
}
  • _ in a pattern says “don’t bind this part.”
  • Great with record patterns and switch pattern matching.
  • Bonus: you can also use _ when a lambda parameter or catch binding is syntactically required but unused in newer Java releases.

Python

for _ in range(3):
    do_side_effect()

name, _ = get_user_and_id()
  • _ is a convention for “unused.”
  • Caveat: some projects use _ for gettext i18n. Don’t mix the roles.

C# (discards)

var (_, y) = GetPoint();
if (int.TryParse(input, out _)) { /* ... */ }
  • _ is a true discard in deconstruction, out params, and patterns.

Go (blank identifier)

for _, v := range items { use(v) }
_, err := doThing(); _ = err // (or handle it!)
  • _ is the blank identifier: receive-but-ignore any value.

Rust

let (_, y) = get_point();
match val {
    Some(_) => println!("has value"),
    None => {}
}
  • _ pattern ignores; prefix _name binds but suppresses warnings.

Kotlin / Scala

val (_, y) = point
list.forEach { _ -> /* ignore element */ }
case (_, y) => y
  • _ in destructuring/patterns; lambdas can use _ for ignored args.

Five practical uses

  1. Pattern matching focus: Bind only what you need to keep switch/match readable.
  2. Tuple/record returns: Grab the 2 fields you need, ignore the rest.
  3. Loop placeholders: Run an effect N times without fake counters.
  4. APIs with mandatory handlers: A required parameter you don’t use.
  5. Tests: Given/When/Then setups with unused returns.

Pitfalls & pro tips

  • Don’t hide errors: In Go and Python, ignoring an err or exception blindly is a smell.

  • Overuse hurts: Too many _s become visual static; prefer names when meaning matters.

  • Team conventions: In Python, confirm _ isn’t reserved for i18n.

  • Java versioning: Unnamed patterns/variables are newer Java features. If you ship libraries, document your minimum Java version.

  • Review checklist:

    • Is ignoring this value safe?
    • Would a name improve clarity?
    • Is the pattern exhaustive without binding noise?

Conclusion

Think of _ as a contract:

“I acknowledge this value exists, but my logic doesn’t depend on it.”

That tiny glyph declutters code and strengthens intent—especially with Java pattern matching, where it turns complex shape checks into readable, maintainable switches.