Naming things is famously one of the two hard problems in computer science. The other hard problem — Cache Invalidation. But with the same gravitas, just deciding how to write the names you’ve already chosen. Should it be userId or user_id? BlogPost or blog-post? MAX_RETRIES or maxRetries?

Every language and every codebase has opinions, and most of the time they’re inherited rather than chosen. This post is reference to what each style is for, where it’s standard, and the few lines of code I find helpful to convert between them.

The five styles you’ll meet

1. camelCase

First word lowercase, every subsequent word capitalised. No separators.

userId, getCurrentUser, isAdmin

Standard in JavaScript, TypeScript, Java, Swift, Kotlin for variables, function names, and methods. The default for almost any modern OOP-flavoured language.

2. PascalCase (or UpperCamelCase)

Every word capitalised, including the first. Same idea as camelCase, just with the first letter promoted.

User, BlogPost, HttpRequest

Standard for class names, types, React components, and constructors across most languages. The signal “this thing represents a type or a thing-with-its-own-identity” is worth keeping consistent.

3. snake_case

All lowercase, words separated by underscores.

user_id, get_current_user, is_admin

Standard in Python, Ruby, Rust (mostly), and SQL column names. Database tables and HTTP form fields tend to default here too.

4. kebab-case (also called dash-case or lisp-case)

All lowercase, words separated by hyphens.

user-profile, add-to-cart, blog-post

Used everywhere a hyphen is legal but an underscore is awkward — URLs, CSS class names, HTML attribute names, file names on the web, npm package names. Not legal as a JS identifier (the - would be parsed as subtraction), so you’ll never see it inside code.

5. SCREAMING_SNAKE_CASE (or UPPER_SNAKE_CASE)

All uppercase, words separated by underscores.

MAX_RETRIES, API_BASE_URL, DEFAULT_TIMEOUT_MS

The universal “this is a constant” signal. In TypeScript, JS, Python, C, Go — if you see this, you’re looking at a value that’s not meant to change at runtime.

A quick “where to use what” cheat-sheet

ContextConvention
JS/TS variable, functioncamelCase
JS/TS class, type, React componentPascalCase
JS/TS constantSCREAMING_SNAKE_CASE
CSS class, HTML attr, URL slugkebab-case
File name (web project)kebab-case.tsx
Database column, Python variablesnake_case
JSON API fielddepends — see below

The JSON API question is the one most teams disagree on. Two common answers:

  • camelCase because that’s what JavaScript clients want to read. Common in Node-first APIs.
  • snake_case because that’s what Python/Ruby/SQL backends produce naturally. Common in older REST APIs.

There’s no right answer — pick one, document it, and have a single mapping layer if your client and server disagree.

Converting between them

Here are the useful one-liners if you ever need to converted between them -

camelCase → kebab-case

const camelToKebab = (s) => s.replace(/([A-Z])/g, "-$1").toLowerCase();

camelToKebab("blogPostTitle"); // "blog-post-title"
camelToKebab("isAdmin"); // "is-admin"

The regex finds every uppercase letter and inserts a - before it, then lowercases the whole thing.

kebab-case → camelCase

const kebabToCamel = (s) => s.replace(/-./g, (m) => m[1].toUpperCase());

kebabToCamel("blog-post-title"); // "blogPostTitle"
kebabToCamel("is-admin"); // "isAdmin"

Match each -X pair and replace it with the uppercase of X.

snake_case → camelCase

const snakeToCamel = (s) => s.replace(/_./g, (m) => m[1].toUpperCase());

snakeToCamel("blog_post_title"); // "blogPostTitle"

Same shape as the kebab version, just with _ instead of -.

camelCase → snake_case

const camelToSnake = (s) => s.replace(/([A-Z])/g, "_$1").toLowerCase();

Symmetrical with the kebab version.

Anything → kebab-case (a slug-style helper)

For “URL-safe slug from a free-form title”, you usually want a more permissive transform — lowercasing, stripping punctuation, collapsing spaces:

function slugify(s) {
  return s
    .toLowerCase()
    .replace(/[^\w\s-]/g, "") // strip punctuation
    .replace(/[\s_]+/g, "-") // spaces & underscores → dashes
    .replace(/-+/g, "-") // collapse multiple dashes
    .replace(/^-|-$/g, ""); // trim leading/trailing dashes
}

slugify("My Blog Post: Take 2!"); // "my-blog-post-take-2"
slugify("Hello, World"); // "hello-world"

This is the version I use across this site for URL slugs (it’s effectively what the formatViewTransistionName helper in src/utils/formatter.ts boils down to).

A few real-world gotchas

  • Acronyms. Is it XMLHttpRequest or XmlHttpRequest? Both are valid PascalCase but different teams hold different opinions. Google’s TS style guide says to treat acronyms as single words (XmlHttpRequest); the original DOM API picked XMLHttpRequest. I follow the codebase’s existing convention rather than dying on this hill.
  • Leading/trailing underscores. _private and __init__ carry meaning in some languages (Python). Don’t use them as styling unless they’re communicating something.
  • Numbers. level1 vs level_1 vs level-1. The right answer is “whatever the rest of the codebase does”. The wrong answer is “I’ll mix all three and hope nobody notices”.
  • The - problem in JS. You can’t have a JS identifier with a - in it. So data-user-id (an HTML attribute) becomes dataset.userId in the DOM API. The browser does the conversion for you. Most ORMs do something similar — Sequelize and Prisma both let you write userId in code and store user_id in Postgres.

Casing isn’t the most exciting topic, but a codebase that picks one convention per layer and sticks to it reads twice as fast as one that mixes them. The conversions are short, mechanical, and easy to centralise — and the moment they’re centralised, the question becomes a one-time decision rather than a recurring debate.

Pick a style. Write a converter for the boundaries. Move on to a more interesting problem.