Case Conversion & Text FormattingJanuary 15, 2025

snake_case vs. kebab-case vs. camelCase: A Developer's Guide

Which naming convention goes with which language, why, and how to convert between them — with real code examples in JavaScript, Python, Rust, CSS, and URLs.

By Muhammad Umair · Founder & Editor at TextKit

snake_case vs. kebab-case vs. camelCase: A Developer's Guide

Naming conventions are one of those things every developer has opinions about and almost no one reads the documentation on. You pick up the convention of whatever language you learned first, you carry it into every codebase you touch, and you spend the rest of your career silently judging other people's variable names.

This guide lays out the four major naming conventions — snake_case, kebab-case, camelCase, and PascalCase — explains where each one is the convention (and why), shows real code in each language, and finishes with conversion logic you can use in your own tooling.

The four conventions, defined

Before we get into "which language uses what," let's pin down the definitions. There are essentially four conventions in widespread use, plus a couple of niche ones.

snake_case

Words are separated by underscores, all lowercase: user_id, created_at, is_active. The name comes from the visual resemblance to a snake's body (the underscore looks like the snake on the ground between words).

Variants:

  • lower_snake_case: user_id — the common form, used for variable and function names.
  • UPPER_SNAKE_CASE (sometimes called SCREAMING_SNAKE_CASE): MAX_RETRIES, DEFAULT_TIMEOUT — used for constants and macros.

kebab-case

Words are separated by hyphens, all lowercase: user-id, created-at, is-active. The name comes from the resemblance to food on a skewer. Also called spinal-case, dash-case, or lisp-case (because Lisp has used it since the 1950s).

Variants:

  • lower-kebab-case: user-id — the common form.
  • Title-Kebab-Case: User-Id — rare; occasionally seen in COBOL and old mainframe conventions.

camelCase

Words are joined with no separators; every word after the first starts with a capital letter: userId, createdAt, isActive. The name comes from the visual hump in the middle. Also called lowerCamelCase or dromedaryCase (one hump).

PascalCase

Same as camelCase but the first word is also capitalized: UserId, CreatedAt, IsActive. Also called UpperCamelCase (because it's still camelCase, just with a leading capital) or BumpyCase.

There are a few niche conventions worth knowing about but rarely encountered:

  • dot.notation: user.id, app.config — used in some config file formats (HOCON, NGINX).
  • Train-Case or HTTP-Header-Case: Content-Type, User-Agent — used in HTTP headers.
  • MACRO_CASE: same as SCREAMING_SNAKE_CASE, used in C preprocessor macros.

The conventions by language

Different languages have different conventions, and the conventions exist for reasons that are sometimes technical (the language's syntax disallows one option) and sometimes cultural (the original author just preferred one).

Here's a cheat sheet of the dominant convention for variables, functions, and types in each major language:

| Language | Variables / functions | Types / classes | Constants | Files |

|---|---|---|---|---|

| JavaScript / TypeScript | camelCase | PascalCase | camelCase (or SCREAMING_SNAKE for true constants) | kebab-case or camelCase |

| Python | snake_case | PascalCase | SCREAMING_SNAKE_CASE | snake_case |

| Rust | snake_case | PascalCase | SCREAMING_SNAKE_CASE | snake_case |

| Go | camelCase (unexported) / PascalCase (exported) | PascalCase | PascalCase (exported) / camelCase | snake_case or kebab-case |

| Java | camelCase | PascalCase | SCREAMING_SNAKE_CASE | PascalCase (matches class name) |

| C / C++ | snake_case or camelCase (varies by codebase) | PascalCase or snake_case | SCREAMING_SNAKE_CASE | snake_case |

| Ruby | snake_case | PascalCase | SCREAMING_SNAKE_CASE | snake_case |

| PHP | camelCase (modern) / snake_case (older) | PascalCase | SCREAMING_SNAKE_CASE | PascalCase (matches class) |

| Swift | camelCase | PascalCase | camelCase | PascalCase |

| Kotlin | camelCase | PascalCase | SCREAMING_SNAKE_CASE (or camelCase) | PascalCase |

| C# | camelCase (private) / PascalCase (public) | PascalCase | PascalCase | PascalCase |

| Elixir | snake_case | PascalCase (modules) | SCREAMING_SNAKE_CASE | snake_case |

| Haskell | camelCase | PascalCase | camelCase | PascalCase |

| Clojure | kebab-case | PascalCase (namespace) | kebab-case | kebab-case |

| Lisp family | kebab-case | varies | kebab-case | kebab-case |

| CSS | kebab-case | n/a | kebab-case | n/a |

| HTML / URLs | kebab-case | n/a | n/a | kebab-case |

| JSON (config files) | camelCase or snake_case (varies by project) | n/a | n/a | n/a |

Let's dig into the most consequential ones.

JavaScript and TypeScript

JavaScript was originally written in 10 days at Netscape in 1995, and the naming convention emerged from a combination of Java's influence (Java used camelCase) and C's influence (C used snake_case in some communities, camelCase in others). Brendan Eich picked camelCase for variables and functions, PascalCase for constructor functions, and the convention has held ever since.

// camelCase for variables and functions
const userId = 12345;
function getUserById(id) { /* ... */ }

// PascalCase for classes and constructor functions
class UserService { /* ... */ }
function Person(name) { this.name = name; }  // pre-ES6 constructor

// SCREAMING_SNAKE_CASE for module-level constants (convention, not requirement)
const MAX_RETRIES = 5;
const API_BASE_URL = "https://api.example.com";

TypeScript follows JavaScript's conventions exactly. The TypeScript team's own coding guidelines specify camelCase for variables, parameters, and functions; PascalCase for types, classes, interfaces, and enums.

Where snake_case leaks into JavaScript

  • JSON keys from Python backends. Django, Flask, FastAPI, and most Python web frameworks serialize JSON with snake_case keys by default. If you consume these in JavaScript, you'll see {"user_id": 1, "created_at": "..."} in the response. You either accept the inconsistency, configure the backend to serialize as camelCase (FastAPI, Django REST Framework, and SQLAlchemy all support this), or write a transform layer.
  • Database column names. Most SQL databases and ORMs default to snake_case: user_id, created_at. If your JavaScript code reads directly from these, you'll have snake_case values in your code.

Files

JavaScript file naming is a mess. Common conventions:

  • camelCase: userService.js — common in older Node.js codebases and Angular.
  • kebab-case: user-service.js — common in modern Node.js, Vue, and React.
  • PascalCase: UserService.js — used for files that contain a single class.
  • kebab-case for non-component files, PascalCase for component files: a popular React convention (user-service.ts for utilities, UserCard.tsx for components).

TypeScript itself is internally inconsistent — the compiler source uses PascalCase for files containing classes and camelCase for others.

Python

Python is the most opinionated of the major languages about naming. PEP 8, written by Guido van Rossum in 2001, lays it down:

  • snake_case for variables, functions, methods, modules.
  • PascalCase for class names.
  • SCREAMING_SNAKE_CASE for constants.
  • lowercase (single word, no separators) for packages.
# snake_case for variables and functions
user_id = 12345

def get_user_by_id(id):
    return User.query.filter_by(id=id).first()

# PascalCase for classes
class UserService:
    def __init__(self, db):
        self.db = db

# SCREAMING_SNAKE_CASE for module-level constants
MAX_RETRIES = 5
API_BASE_URL = "https://api.example.com"

PEP 8 is enforced by social pressure and by tools. Most Python projects run flake8 or ruff in CI, both of which will reject non-snake_case variable names. Modern Python style is more uniform than any other language's because the style guide is so universally adopted.

Why Python chose snake_case

Two reasons, both cultural:

  1. C influence. Python grew up in a Unix/C ecosystem where snake_case was common (the C standard library uses size_t, FILE *, snake_case_names).
  2. Readability studies. Python's early community leaned heavily on academic CS research, and there were a handful of studies in the 1990s suggesting snake_case is slightly more readable than camelCase for variable names. The evidence is weak, but it was cited enough times to become canon.

Where Python deviates

  • Type hints with generics sometimes use PascalCase type variables: T = TypeVar("T").
  • Some legacy scientific libraries (NumPy, parts of SciPy) use camelCase for historical reasons: np.asarray, np.frombuffer. Newer NumPy style is snake_case.
  • Django models use snake_case for table and column names but PascalCase for model classes.

Rust

Rust follows Python's lead: snake_case for variables and functions, PascalCase for types and traits, SCREAMING_SNAKE_CASE for constants. The compiler will actually warn you if you violate these conventions, and rustc has a non_snake_case and non_camel_case_types lint enabled by default.

// snake_case for variables and functions
let user_id: u32 = 12345;

fn get_user_by_id(id: u32) -> Option<User> {
    // ...
}

// PascalCase for types, traits, and enums
struct UserService { /* ... */ }
trait Display { /* ... */ }
enum Color {
    Red,
    Green,
    Blue,
}

// SCREAMING_SNAKE_CASE for constants
const MAX_RETRIES: u32 = 5;
const API_BASE_URL: &str = "https://api.example.com";

// snake_case for modules
mod user_service { /* ... */ }

Rust also uses kebab-case in some places: crate names published to crates.io conventionally use kebab-case (serde-json, tokio-util), even though the Rust code inside uses snake_case (serde_json, tokio_util). This is because crates.io allows hyphens in package names but Rust code can't import a name with a hyphen, so the compiler rewrites them to underscores on import.

Go

Go has the most unusual naming convention of the major languages, because visibility is determined by capitalization. A name starting with an uppercase letter is exported (public); a name starting with a lowercase letter is unexported (private to the package). This means naming conventions and access control are the same thing.

// camelCase = unexported (private to package)
var userID int = 12345

func getUserByID(id int) *User {
    // ...
}

// PascalCase = exported (public)
var DefaultTimeout = 30 * time.Second

func GetUserByID(id int) *User {
    // public version
}

// PascalCase for types
type UserService struct { /* ... */ }

Go's conventions are documented in Effective Go and enforced by gofmt and golint. A few specifics:

  • Initialisms stay uppercase: userID not userId, apiURL not apiUrl. The convention is that acronyms like ID, URL, HTTP, JSON, API are written in their canonical uppercase form regardless of position. This is a common source of friction when Go developers move to other languages (and vice versa).
  • No underscores in Go names. The Go community actively dislikes snake_case. Even SCREAMING_SNAKE_CASE constants are unusual; Go prefers DefaultTimeout over DEFAULT_TIMEOUT.
  • Getters don't have a "Get" prefix. A getter for a field owner is named Owner(), not GetOwner(). Setters do use a "Set" prefix: SetOwner().

Java

Java is aggressively camelCase. The convention was set by Sun's original Code Conventions for the Java Programming Language in the 1990s and has barely budged.

// camelCase for variables and methods
int userId = 12345;

public User getUserById(int id) {
    // ...
}

// PascalCase for classes and interfaces
public class UserService { /* ... */ }
public interface UserRepository { /* ... */ }

// SCREAMING_SNAKE_CASE for constants (static final)
public static final int MAX_RETRIES = 5;
public static final String API_BASE_URL = "https://api.example.com";

// File name MUST match the public class name: UserService.java

Java's file naming is unusual: a file that contains a public class named UserService must be named UserService.java exactly. The compiler enforces this.

CSS and HTML

CSS class names use kebab-case, full stop. This isn't a convention; it's effectively a requirement, because CSS selectors and HTML attributes have specific rules.

/* kebab-case for classes */
.user-card { /* ... */ }
.is-active { /* ... */ }
.header-navigation { /* ... */ }

/* CSS custom properties (variables) also kebab-case */
:root {
  --primary-color: #10b981;
  --max-width: 1200px;
}

Why kebab-case in CSS? Because:

  1. CSS identifiers can't contain certain characters, but hyphens are fine.
  2. Hyphens were already used in CSS keywords (font-size, background-color, text-align), so extending that to class names felt natural.
  3. camelCase class names would visually clash with kebab-case CSS properties.

HTML follows the same logic: data-user-id, aria-labelledby, class="user-card".

BEM and naming methodologies

CSS naming methodologies like BEM (Block, Element, Modifier) build on kebab-case with a specific structure: block__element--modifier. For example: menu__item--active. This adds semantic structure on top of kebab-case.

URLs

URLs should use kebab-case. This is a strong convention across the web for several reasons:

  1. Search engines prefer hyphens. Google's John Mueller has stated publicly that Google treats hyphens as word separators in URLs, but underscores as part of the word. So my-page is read as "my page," while my_page is read as "my_page" — a single token.
  1. Hyphens are URL-safe. Underscores can be obscured by link underlines in browsers — you can't tell whether my_page is my_page or my page (with the underline running through).
  1. Convention. Most CMSes (WordPress, Ghost, Hashnode, dev.to) default to kebab-case URLs.
✅ /blog/how-to-write-good-urls
❌ /blog/how_to_write_good_urls
❌ /blog/howToWriteGoodUrls
❌ /blog/How-To-Write-Good-URLs

JSON keys

This is where the snake_case vs. camelCase war is still actively fought. The two camps:

  • Frontend / JavaScript native: camelCase keys ({ "userId": 1, "createdAt": "..." }). This matches what the consuming JavaScript code expects.
  • Backend / database native: snake_case keys ({ "user_id": 1, "created_at": "..." }). This matches what the database columns are named.

Most modern APIs have a transform layer that converts snake_case in the database to camelCase in the JSON response. The JSON:API spec recommends camelCase. Google's JSON Style Guide recommends camelCase. But you'll still see plenty of snake_case APIs, especially in Python-heavy stacks (Django REST Framework defaults to snake_case, FastAPI defaults to snake_case).

There's no single right answer; consistency within an API matters more than which case you choose.

Conversion logic: how to write your own

Converting between cases is a common programming exercise, and the edge cases will surprise you. Here's a reasonably robust implementation in JavaScript:

// Tokenize a string into words, regardless of source convention
function tokenize(str) {
  // Handle SCREAMING_SNAKE_CASE first (all caps + underscores)
  // Then snake_case and kebab-case (split on separators)
  // Then camelCase and PascalCase (split on case boundaries)
  return str
    .replace(/_/g, " ")
    .replace(/-/g, " ")
    .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
    .replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")  // handles "URLParser" → "URL Parser"
    .trim()
    .toLowerCase()
    .split(/\s+/);
}

function toCamelCase(str) {
  const words = tokenize(str);
  return words
    .map((w, i) => i === 0 ? w : w.charAt(0).toUpperCase() + w.slice(1))
    .join("");
}

function toPascalCase(str) {
  const words = tokenize(str);
  return words
    .map(w => w.charAt(0).toUpperCase() + w.slice(1))
    .join("");
}

function toSnakeCase(str) {
  return tokenize(str).join("_");
}

function toKebabCase(str) {
  return tokenize(str).join("-");
}

function toScreamingSnakeCase(str) {
  return tokenize(str).join("_").toUpperCase();
}

// Tests
console.log(toCamelCase("user_id"));          // → userId
console.log(toKebabCase("UserId"));           // → user-id
console.log(toSnakeCase("URLParser"));        // → url_parser
console.log(toPascalCase("created-at"));      // → CreatedAt
console.log(toScreamingSnakeCase("maxRetries")); // → MAX_RETRIES

Note the two regex substitutions in tokenize:

  • ([a-z0-9])([A-Z]) handles the normal camelCase boundary: userIduser Id.
  • ([A-Z]+)([A-Z][a-z]) handles consecutive capitals followed by lowercase, the "URLParser" case: URLParserURL Parser. Without this rule, you get URLParseru r l parser or similar garbage.

These two rules together correctly tokenize the vast majority of real-world names. Edge cases that still trip up naive implementations: numbers adjacent to letters (user2Id), consecutive capitals at the end of a string (userID — tokenizes as user i d rather than user id), and acronyms in PascalCase (HTMLEntity — should tokenize as html entity).

Python version

import re

def tokenize(s: str) -> list[str]:
    s = re.sub(r"[_-]", " ", s)
    s = re.sub(r"([a-z0-9])([A-Z])", r"\1 \2", s)
    s = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1 \2", s)
    return s.lower().split()

def to_camel_case(s: str) -> str:
    words = tokenize(s)
    return words[0] + "".join(w.capitalize() for w in words[1:])

def to_snake_case(s: str) -> str:
    return "_".join(tokenize(s))

def to_kebab_case(s: str) -> str:
    return "-".join(tokenize(s))

def to_pascal_case(s: str) -> str:
    return "".join(w.capitalize() for w in tokenize(s))

def to_screaming_snake(s: str) -> str:
    return "_".join(tokenize(s)).upper()

Most production codebases use a library instead of hand-rolling this: camelcase for JavaScript (over 50 million weekly downloads), inflection for Python, heck for Rust, cases for Dart.

When conventions collide

The most common real-world case-conversion pain points:

  1. JSON API consumption across language boundaries. A Python backend ships snake_case JSON; a JavaScript frontend wants camelCase. The fix is a transform layer at the API client level (axios interceptors, fetch wrappers, or a library like humps).
  1. Database columns mapped to language objects. SQL columns are snake_case by default; JavaScript objects are camelCase by convention. ORMs (Sequelize, TypeORM, Prisma, SQLAlchemy) all have snake_case mapping options — but you have to enable it.
  1. URLs and routes. Most web frameworks let you define routes in any case and rewrite to kebab-case for the public URL. In Next.js, app/blog/[slug]/page.tsx produces the URL /blog/[slug]; use a kebab-case slug for the article.
  1. CSS-in-JS. styled-components and emotion let you write JavaScript objects with camelCase property names (backgroundColor) and convert to kebab-case CSS (background-color) at runtime.
  1. Shell scripts and environment variables. Environment variables are conventionally SCREAMING_SNAKE_CASE (DATABASE_URL, API_KEY). When read into JavaScript or Python, they're often converted to camelCase for internal use.

Why any of this matters

Naming conventions look like bikeshedding, but they have real consequences: code review friction (inconsistent naming is the #1 source of nitpick comments); tooling support (linters, formatters, and IDE refactoring tools assume the convention); onboarding (a new developer reading snake_case code immediately knows they're in Python land); and searchability (if half your variables are userId and half are user_id, you have to search for both).

If you take one thing from this article: follow the convention of the language you're writing in, even if you prefer a different one. Naming conventions are social contracts, not technical choices.

For converting between cases in your own code, the TextKit case converter handles all five conventions and the tricky acronym edge cases. Useful when you're pasting a JSON payload from a Python API into a TypeScript file and need to camelCase everything in one shot.

Last reviewed: January 15, 2025. This article is part of TextKit's original content library. Spotted an error or have feedback? Tell us.