Naming Conventions Explained: camelCase vs snake_case vs PascalCase vs kebab-case, by Language
Naming conventions are one of the few things in programming where the right answer is decided mostly by where the code lives. Python wants `snake_case`, JavaScript wants `camelCase`, CSS wants `kebab-case`, and a single feature often touches all three. The real work is not picking a favourite style but converting cleanly at each boundary, especially for acronyms and numbers, where automatic converters get it wrong.
The styles side by side
camelCase userId, maxRetryCount, parseJson
PascalCase UserAccount, HttpClient, OrderStatus (also UpperCamelCase)
snake_case user_id, max_retry_count, parse_json
SCREAMING_SNAKE MAX_RETRY_COUNT, API_BASE_URL
kebab-case user-id, max-retry-count (also dash-case, spinal-case)
Train-Case Content-Type, X-Request-Id (HTTP header style)
They all solve the same problem: identifiers cannot contain spaces, so word boundaries need another marker. Case changes save characters; underscores and hyphens are easier to read at a glance, especially for long names and for readers whose first language is not English. Studies have measured small reading-speed differences in both directions. The consistent finding is that familiarity matters more than style, which is the argument for following each ecosystem's convention.
What each language expects
JavaScript/TS camelCase variables and functions, PascalCase classes and types,
SCREAMING_SNAKE for true constants
Python (PEP 8) snake_case functions, variables and modules, PascalCase (CapWords) classes,
SCREAMING_SNAKE constants
Java camelCase methods and fields, PascalCase classes, SCREAMING_SNAKE constants
C# (.NET) PascalCase for methods, properties and public members, camelCase for
locals and parameters, _camelCase for private fields (common convention)
Go MixedCaps only; the first letter decides visibility (Exported vs unexported)
Rust snake_case functions and variables, UpperCamelCase types,
SCREAMING_SNAKE constants; the compiler warns otherwise
Ruby/PHP Ruby: snake_case methods, PascalCase classes. PHP (PSR-1/PSR-12):
camelCase methods, PascalCase classes
Go is the one where style has semantics: userName is private to its package and UserName is exported. Renaming for style there can break other packages.
Outside the code: files, CSS, URLs, databases, environment
CSS classes and custom properties use kebab-case (.nav-item, --brand-color), because CSS identifiers are case-sensitive and hyphens are the platform's own style (background-color). URLs should use kebab-case as well. Google's URL guidelines recommend hyphens rather than underscores to separate words, and lower case avoids duplicate URLs, because paths are case-sensitive on most servers.
Databases favour snake_case, and in PostgreSQL that is close to a requirement. Unquoted identifiers are folded to lower case, so CREATE TABLE UserAccount creates useraccount, and a column created as "userId" must be quoted with exactly that case in every query forever after. MySQL's case sensitivity for table names depends on the operating system and lower_case_table_names, which is another reason to stay lower case.
Environment variables use SCREAMING_SNAKE_CASE (DATABASE_URL), and files and directories in web projects usually use kebab-case, which avoids problems on case-insensitive file systems such as the macOS and Windows defaults.
JSON APIs: camelCase or snake_case?
Both are common. Google's JSON style guide and most JavaScript-first APIs use camelCase (createdAt), while Stripe, GitHub, Twitter/X and many Python- or Ruby-backed APIs use snake_case (created_at). Neither is wrong. What causes bugs is mixing them in one API, or letting the server's language leak into the payload by accident.
Pick one for the public contract, document it, and convert at the serialisation layer rather than by hand in every handler:
# Python / Pydantic: snake_case in code, camelCase on the wire
from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel
class Order(BaseModel):
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
order_id: int
created_at: str
Order(order_id=1, created_at='2026-09-28').model_dump(by_alias=True)
# {'orderId': 1, 'createdAt': '2026-09-28'}
// Java / Jackson: snake_case JSON for camelCase fields
ObjectMapper mapper = new ObjectMapper()
.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
Acronyms and numbers: where converters break
The hard cases are acronyms and digits, and style guides disagree. Microsoft's .NET guidelines upper-case two-letter acronyms and capitalise only the first letter of longer ones (IOStream, HtmlParser). Go keeps acronyms in consistent case (userID, ServeHTTP, parseURL). Google's Java style guide treats acronyms as words (XmlHttpRequest, userId). The browser API XMLHttpRequest follows none of them consistently.
Automatic conversion depends on which convention the input used, so round-trips are lossy:
userID -> snake: user_i_d or user_id (depends on the converter)
user_id -> camel: userId (not userID: the acronym is gone)
HTMLParser -> snake: html_parser or h_t_m_l_parser
sha256Hash -> snake: sha256_hash or sha_256_hash
v2Endpoint -> kebab: v2-endpoint or v-2-endpoint
Treating acronyms as ordinary words (userId, HtmlParser, sha256Hash) is the choice that converts losslessly in both directions. That makes it the safest convention for any name that crosses a boundary, such as JSON fields, database columns and generated clients. When you batch-convert names, review digits and acronyms by hand.
Renaming safely
Changing a convention is a breaking change whenever the name is visible outside the code: JSON fields, query parameters, database columns, environment variables, CSS classes used by tests or third-party scripts, and anything stored in caches or queues. For APIs, accept both names for a transition period, emit the new one, and log use of the old one before removing it.
Inside code, use the IDE's rename refactoring instead of search and replace. It understands scope and will not rename user_id inside an SQL string or a translation key. Then let a linter enforce the result: ESLint's camelcase or @typescript-eslint/naming-convention, Pylint and Ruff (N rules from pep8-naming), RuboCop, or clippy for Rust.
Sources and further reading
PEP 8 (Python), the Google JavaScript and Java style guides, Microsoft's .NET Framework Design Guidelines (Capitalization Conventions), Effective Go (MixedCaps), the Rust API Guidelines (naming) and PHP-FIG's PSR-1 define the language conventions above. Google's JSON style guide and Search Central's URL structure guidelines cover APIs and URLs. The PostgreSQL manual (Identifiers and Key Words) documents case folding.
Frequently asked questions
What is the difference between camelCase and snake_case?
camelCase joins words by capitalising each word after the first (`userId`); snake_case joins lower-case words with underscores (`user_id`). JavaScript, Java and C# locals use camelCase; Python, Ruby, Rust and SQL databases use snake_case.
Should a JSON API use camelCase or snake_case?
Either works if it is consistent. camelCase matches JavaScript clients and Google's JSON style guide; snake_case is used by Stripe, GitHub and many Python or Ruby back ends. Convert in the serialiser rather than by hand.
What is PascalCase?
Like camelCase but with the first letter also capitalised: `UserAccount`. Most languages use it for class and type names, and C# uses it for methods and public properties as well.
When should I use kebab-case?
For CSS class names and custom properties, URL paths, file names in web projects, and command-line flags. It cannot be used for variables in most languages, because the hyphen is the minus operator.
Should I write userId or userID?
Treating acronyms as words (`userId`, `HtmlParser`) converts cleanly to and from snake_case, so it is the safest for names that cross API or database boundaries. Go conventionally uses `userID`; follow your language's guide inside code.
Why should database columns be snake_case?
PostgreSQL folds unquoted identifiers to lower case, so camelCase names must be quoted exactly every time. Lower-case snake_case avoids that and behaves the same across databases and operating systems.