There are two hard problems in computer science: cache invalidation, naming things, and off-by-one errors. The naming-things one is the only one we can actually argue about all afternoon, so we do.
This post is a practical reference for when each casing convention is correct, where they collide (databases meet APIs meet frontends meet URLs), and how to handle the conversions without sprinkling _.camelCase() calls across your codebase like prayers.
The five conventions you'll see
| Convention | Example | Where it lives |
|---|---|---|
| camelCase | userEmail |
JavaScript variables, JSON keys, Java/Swift methods |
| PascalCase | UserEmail |
Class names, type names, React components, C# everything |
| snake_case | user_email |
Python variables, SQL columns, Ruby methods, env vars (sort of) |
| kebab-case | user-email |
URLs, CSS classes, HTML attributes, file names, npm packages |
| SCREAMING_SNAKE_CASE | USER_EMAIL |
Constants, environment variables |
That's the picture. The rest of the post is about why each one ended up where it did, and what to do when you have to cross between them.
camelCase — the JavaScript default
JavaScript has used camelCase since the language existed. Variable names, function names, object keys, parameter names — all camelCase. The DOM API uses it (document.querySelector, addEventListener), browser globals use it, every framework follows it.
const userEmail = 'jane@example.com'
function isValidEmail(input) { /* ... */ }
fetch('/api/users').then(res => res.json())
Java, Swift, Kotlin, and TypeScript also use camelCase for variables and methods (PascalCase for classes). If you're writing in any of these languages and the team convention is "use camelCase", that's not a stylistic preference; that's the language convention.
One exception worth knowing. Some JavaScript codebases use snake_case for properties that come straight from the database or from a Python/Ruby API to avoid mid-flight conversion bugs. It's a defensible choice, but it does leak the backend's convention into the frontend forever.
PascalCase — for things that get instantiated
PascalCase (which is just camelCase with the first letter capitalized) signals "this is a type, not a value." Used for:
-
Classes:
class UserRepository {} -
TypeScript types and interfaces:
interface User {},type LoginResult = ... -
React components:
function UserAvatar({ user }). JSX literally requires this —<userAvatar />is treated as an HTML element,<UserAvatar />as a component. -
Enum values in some style guides:
enum Status { Active, Pending, Banned }
The mental model: if you can new it, type-check against it, or render it as a component, it's PascalCase. If it's a value you read or call, it's camelCase. Mostly that line is sharp; once in a while you'll see a class meant to be used like a singleton (Math, JSON) and the line blurs.
snake_case — Python, SQL, and the systems layer
snake_case dominates Python (PEP 8 mandates it), SQL columns (most style guides at least), Ruby, Rust (mostly), and any language that came out of the Unix systems-programming world.
def get_user_by_id(user_id):
return db.query("SELECT user_email FROM users WHERE id = %s", (user_id,))
The trade-off vs camelCase is readability of long names: get_user_by_id is slightly easier to read than getUserById for most people, especially with screen readers. The cost is that snake_case is one extra character per word boundary, which adds up in 50,000-line codebases.
Environment variables are kind of snake_case but uppercase: DATABASE_URL, API_KEY, NODE_ENV. This is convention from POSIX, and it's universal — never use lowercase or camelCase for env vars. Tools assume uppercase.
kebab-case — anywhere a hyphen is legal and an underscore isn't
This is the rule. kebab-case lives wherever syntax permits hyphens but not underscores (or where hyphens are the established convention):
-
URLs:
/blog/why-rust-matters. Search engines parse hyphens as word separators; underscores get treated as part of one word. -
CSS class names:
.user-avatar,.is-active. Conventions like BEM lean hard on kebab-case. -
HTML attributes:
data-user-id,aria-label. -
CLI flags:
--dry-run,--no-color. -
npm package names:
lodash,eslint-plugin-react. Underscores are technically allowed but discouraged. -
File names in many ecosystems:
user-service.ts,404-not-found.html.
Why hyphens for URLs specifically? Because /blog/why_rust_matters shows up in Google search ranking as one word ("why_rust_matters") rather than three. The SEO impact is real.
SCREAMING_SNAKE_CASE — constants and signals
The all-caps form is a signal that says "this value is fixed at build/deploy time, not runtime."
const MAX_RETRIES = 5
const DEFAULT_TIMEOUT_MS = 30_000
const FEATURE_FLAGS = ['beta-search', 'new-checkout']
DATABASE_URL = os.environ['DATABASE_URL']
DEBUG = False
In JavaScript, this convention is fading — some style guides treat all const as constant enough to use camelCase, reserving SCREAMING_CASE only for truly immutable, module-scoped values. Pick a rule and apply it consistently; what kills readability is a codebase where some constants are uppercase and some aren't with no rule.
Where conventions collide
Real applications cross between conventions constantly. The friction points:
Database to API. Postgres column user_email becomes JSON key userEmail becomes JavaScript variable userEmail. Most ORMs (Sequelize, Prisma, ActiveRecord, SQLAlchemy) handle this automatically with options like underscored: true.
API to URL. A resource called userProfile in your code lives at /user-profile, not /userprofile or /userProfile. URL-case conversion is a 5-line function but the boundary where you do it matters: at the route definition, not at the controller.
File names to imports. import { UserAvatar } from './user-avatar.tsx'. The file is kebab-case, the export is PascalCase. Most teams hold this convention; some force file names to match their default export. Either is fine; pick one.
Env to config. DATABASE_URL becomes config.databaseUrl in code. Don't keep them named identically — that's how secret leaks happen, when someone does console.log(config) and your env shows up in logs.
Conversion is mechanical — make it boring
The single biggest mistake teams make is converting cases by hand, in scattered places, inconsistently. Pick one of these approaches and apply it everywhere:
1. Convert at the boundary. API response gets converted to camelCase as soon as it arrives, before any business logic touches it:
import { camelCase } from 'lodash'
const data = await res.json().then(deepCamelCase)
2. Use a schema layer. Zod, io-ts, or whatever validation library you use can transform field names as part of parsing.
3. Generate the converters. If your API has an OpenAPI spec, generators like openapi-typescript-codegen produce camelCased clients automatically.
For one-off conversions while writing or refactoring, browser tools handle the boilerplate. The text utilities at text.renderlog.in include dedicated converters for each case style — camelCase, snake_case, kebab-case, PascalCase, Title Case, sentence case — useful when you're translating naming for a config schema, generating SQL DDL from a TypeScript type, or wrangling a CSV with mixed-case headers. The whole site runs client-side, so config files and column names don't get uploaded anywhere.
Anti-patterns I see in code review
Mixed cases in one scope. const user_email next to const userName in the same file. Pick one.
Stutter in identifiers. user.userEmail, Order.orderId. The parent object already gives you the namespace; drop the prefix and write user.email, order.id.
Acronyms inconsistently cased. Is it parseHTML or parseHtml? userID or userId? Both work; what's wrong is using both in the same codebase. Most style guides land on "treat acronyms as words" — parseHtml, userId, loadJsonFile. ESLint and most linters can enforce this.
Casing that fights the language. Writing getUserById in Python or get_user_by_id in JavaScript is a hill that's not worth dying on. Match the language's culture.
TL;DR
| You're naming a... | Use... |
|---|---|
| JS/TS variable, JSON key | camelCase |
| Class, type, React component | PascalCase |
| Python variable, SQL column | snake_case |
| URL, CSS class, file name, CLI flag | kebab-case |
| Build-time constant, env var | SCREAMING_SNAKE_CASE |
Pick a rule per layer, enforce it with a linter, convert at boundaries, and don't mix conventions inside one file. When you're switching between cases in your head, browser-based case converters save the muscle memory for the actually hard problem — which is still cache invalidation.
If this was useful, I've also built a handful of other free, browser-based tools — no signup, no uploads, everything runs client-side:
- JSON Tools — https://json.renderlog.in (formatter, validator, JWT decoder, JSONPath tester, 40+ converters)
- Text Tools — https://text.renderlog.in (case converters, slug generator, HTML/markdown utilities, 70+ tools)
- PDF Tools — https://pdftools.renderlog.in (merge, split, OCR, compress to exact size, 40+ tools)
- Image Tools — https://imagetools.renderlog.in (compress, convert, resize, background remover, 50+ tools)
- QR Tools — https://qrtools.renderlog.in (WiFi, vCard, UPI, bulk QR codes with logos)
- Calc Tools — https://calctool.renderlog.in (60+ calculators for finance, health, math, dates)
- Notepad — https://notepad.renderlog.in (private, offline-first notes, no signup)
Top comments (0)