Six months ago, I published "Why Your Ruby Variables Should Be Full English Sentences" on this very blog. I argued passionately that user_whose_payment_is_being_processed was superior to usr. I presented metrics. I provided style guides. I converted skeptics.
I was a fool.
After living with sentence-length variable names for a full year in production, I have undergone a complete philosophical reversal. I now name every variable x. Every function parameter is x. Temporary values are x. Return values are x. If I need a second variable, it's y. A third is z. I have never needed a fourth.
The Naming Fallacy #
The software industry has an obsession with naming things. Phil Karlton's famous quote — "There are only two hard things in Computer Science: cache invalidation and naming things" — is treated as a challenge to overcome. We hold naming debates in code reviews. We bikeshed variable names for hours. We write blog posts (I wrote one!) about naming conventions.
But what if the hard thing isn't finding the right name? What if the hard thing is accepting that names don't matter?
Consider mathematics. The most profound equations in human history use single-letter variables. Einstein didn't write energy_in_joules = mass_in_kilograms * speed_of_light_in_vacuum_squared. He wrote E = mc². Nobody has ever misunderstood it. The context does the work, not the name.
The Experiment #
After my long-names post went semi-viral, I committed fully. Every variable on our team became a descriptive sentence. For six months, we lived in a world of list_of_all_active_subscriptions_that_are_past_due and boolean_indicating_whether_the_user_has_verified_their_email_address.
Here's what actually happened:
| Metric | Before (short names) | Long names (6 months) | After (everything is x) |
|---|---|---|---|
| Avg time to write a method | 12 minutes | 34 minutes | 6 minutes |
| Avg characters per line | 47 | 112 | 18 |
| Time spent in naming debates (per PR) | 8 minutes | 45 minutes | 0 minutes |
| Git merge conflicts per sprint | 7 | 23 | 1 |
| Developer happiness (1-10 survey) | 6.2 | 4.1 | 9.7 |
The long names were destroying us. Merge conflicts tripled because every rename cascaded across dozens of lines. Code reviews devolved into debates about whether it should be user_who_initiated_the_request or user_that_initiated_the_request (the who/that distinction nearly tore the team apart). And developer happiness cratered — people were spending more time naming things than building things.
The x Philosophy #
The rules are simple:
- Every variable is
x - If you need a second variable in the same scope, it's
y - If you need a third, it's
z - If you need a fourth, your method is too complex. Refactor.
- Functions are
f,g, orh - Classes are single uppercase letters:
A,B,C
Here's the long-names version of a service object from my previous post:
Rubyclass UserAccountDeactivationDueToNonPaymentService
def permanently_deactivate_account_and_notify_user_via_email(
user_account_to_be_deactivated:,
reason_for_deactivation_as_human_readable_string:,
email_template_to_use_for_notification: default_deactivation_email_template
)
record_of_the_deactivation_event = DeactivationEventLog.create_new_entry_for(
account: user_account_to_be_deactivated,
reason: reason_for_deactivation_as_human_readable_string,
initiated_at: Time.current
)
user_account_to_be_deactivated.update_status_to_permanently_deactivated!
EmailDeliveryService.send_deactivation_notification_to_account_holder(
recipient: user_account_to_be_deactivated.primary_email_address,
template: email_template_to_use_for_notification,
deactivation_details: record_of_the_deactivation_event
)
record_of_the_deactivation_event
end
end
And here's the same logic in the x paradigm:
Rubyclass A
def f(x:, y:, z: g)
x2 = B.f(x: x, y: y, z: Time.current)
x.f!
C.f(x: x.x, y: z, z: x2)
x2
end
end
Seven lines instead of twenty-one. The logic is identical. And here's the beautiful part: you can't misread it. With long names, you might skim user_account_to_be_deactivated and miss that it's the same variable as three lines up. With x, there is no ambiguity. It's x. It was x before. It will be x after. x is eternal.
But How Do You Know What x Is? #
You don't need to. And that's the point.
Modern IDEs have hover-to-inspect, jump-to-definition, and type inference. Your editor already knows what x is. The compiler knows what x is. The only entity that allegedly needs a descriptive name is the human reader — and that human reader has access to all the same tools.
But more fundamentally: if your method is short enough, the name is irrelevant. In a 3-line method, x can only be one thing. There is no cognitive load. You see the input, you see the transformation, you see the output. The name adds nothing.
"A variable name is just a comment that the compiler happens to check. And we all know comments go stale."
Think about it. When you rename a variable from active_users to verified_users because the requirements changed, every developer who cached the old meaning in their mental model is now carrying stale context. But x never goes stale. x has no meaning to become outdated. x is a pure vessel for whatever value it holds right now.
The Proof: Open Source #
Don't take my word for it. Look at the most successful codebases in history:
- The Linux kernel: riddled with single-letter variables.
pfor process,ifor index,cfor character. Three decades of success. - Mathematical libraries: NumPy, TensorFlow, PyTorch — all use
x,y,w,beverywhere. Nobody complains. - Haskell: An entire language community where
f x = x + 1is considered readable. They win programming contests. - APL: The entire language is single characters. APL programmers report the highest job satisfaction of any language community.
Meanwhile, Java — the language that gave us AbstractSingletonProxyFactoryBean — is universally despised. Coincidence? I think not.
The Productivity Breakthrough #
After switching our entire codebase to the x convention, something unexpected happened: our code got better. Not just shorter — architecturally better.
Here's why: when every variable is x, you cannot write long methods. Try writing a 50-line method where every variable is x, y, or z. You can't. You'll lose track by line 8. So you're forced to decompose into tiny, focused methods. The naming convention is a complexity circuit breaker.
Our average method length went from 11 lines (already good, thanks to the long-names era) down to 3.2 lines. Every method does exactly one thing. Our codebase reads like algebra:
Rubyclass A
def f(x) = g(x).then { h(_1) }
def g(x) = B.f(x: x.x, y: x.y)
def h(x) = x.valid? ? C.f(x) : D.f(x)
end
Three methods. Three lines each. No naming debates. No merge conflicts. No stale variable names. Pure logic. It's beautiful.
Addressing the Skeptics #
"But what about onboarding new developers?"
Our onboarding time dropped from 1.5 weeks (under the long-names regime) to 2 days. Why? Because there's nothing to learn. The naming convention is one rule: it's x. New developers stop trying to understand names and start understanding structure. They read the types, the tests, and the method signatures. They learn the actual architecture instead of relying on variable names as a crutch.
"What about code review?"
PR reviews are 4x faster. There's nothing to bikeshed. No "should this be called result or response or output?" discussions. The review focuses entirely on logic and correctness. Our team hasn't had a naming-related review comment in four months.
"This is just obfuscation."
Obfuscation is when you deliberately hide meaning. The x convention doesn't hide meaning — it transcends it. A rose by any other name would smell as sweet. A user by any other name is still x.
The Style Guide (Updated) #
I'm formally retiring my previous style guide. Here is the complete replacement:
- Variables:
x,y,z. No exceptions. - Methods:
f,g,h. If you needi, split the class. - Classes: Single uppercase letter. 26 classes per namespace maximum. If you need more, your domain model is too complex.
- Modules: Two uppercase letters.
AAthroughZZgives you 676 modules. That's enough for anyone. - Constants:
X. There should only be one constant. If you have two, one of them is a variable.
Ruby# Before (the dark times)
total_number_of_failed_login_attempts_for_this_user = 0
list_of_users_whose_accounts_are_currently_locked.each do |each_user_whose_account_is_locked|
total_number_of_failed_login_attempts_for_this_user +=
each_user_whose_account_is_locked.failed_login_attempts_since_last_successful_login
end
# After (enlightenment)
x = 0
y.each { x += _1.z }
The second version is shorter, clearer, and impossible to have a merge conflict in. It is the platonic ideal of code.
Conclusion: The Naming Spectrum Is a Horseshoe #
I've been on both extremes now. I've written user_whose_payment_is_being_processed and I've written x. And I can tell you with absolute certainty: both extremes are better than the mushy middle ground of activeUser and paymentResult. At least at the extremes, you have a philosophy.
But between the two extremes, x wins. It's faster to write, impossible to misspell, trivial to refactor, and immune to bikeshedding. It forces small methods, discourages complexity, and frees your mind to think about what the code does rather than what it's called.
Matz designed Ruby for developer happiness. And nothing has made my team happier than never naming a variable again.
This is an April 1st post. Please don't actually do any of this. No production codebases were harmed in the making of this post. Although several interns were confused.
More Posts
- Why Your Ruby Variables Should Be Full English Sentences
- Senior Engineers Should Write More Copy-Paste Code
- Why useEffect Is the Only React Hook You'll Ever Need
- Tabs vs Spaces vs U+2800: The Indentation Debate Is Finally Over
- Always Write Comments: The "Self-Documenting Code" Myth Is Killing Your Codebase