What is Hungarian notation, and why do most modern style guides discourage encoding type, scope, or membership into identifier names?
answer
- strName / iCount / m_total / lpsz
- Systems Hungarian = machine type; Apps Hungarian = semantic kind
- prefix rots into a lie when the type changes
- compiler + IDE already show the type
- promote safe/unsafe into a *type*, not a prefix
basics
~20 sHungarian notation prefixes a name with its type — strName, iCount, lpszBuffer. Modern languages and IDEs already show types, so the prefix adds noise and becomes a lie the moment the type changes. Name the meaning instead.
solid answer
~50 sHungarian notation encodes a variable's *type* into its name (`strName`, `iCount`), and related conventions encode scope or membership (`m_total` for a member, `g_config` for a global, `_id` for a private field, `IShape` for an interface, `ShapeImpl` for an implementation). They date from untyped or weakly tooled environments — early C, assembler, Visual Basic — where the compiler and editor gave you nothing. Today the type is enforced by the compiler and displayed on hover, so the prefix duplicates checked information with unchecked text: change `str` to a list and you must edit every use, or the name lies. It also hurts scanning, since every identifier starts with the same few characters. Note the distinction Joel Spolsky drew: *Apps* Hungarian encoded semantic kind (`unsafeInput` vs `safeHtml`) and is genuinely useful, but the modern equivalent is a distinct *type*, not a prefix. Some conventions (a leading `_` for private fields, `I` for interfaces in C#) survive as ecosystem style — follow the local guide.
code
pseudocode · 13 lines// Systems Hungarian: duplicates what the type system already enforces
strPhone = "+1-555-0100" // later becomes a PhoneNumber object -> name lies
iRetries = 3
m_total = 0
// Modern: name the meaning; let the type carry the type
phoneNumber: PhoneNumber
maxRetries: Int
totalCents: Int // unit in the name, since Int can't say it
// Apps Hungarian's good idea, promoted into the type system:
render(input: UnsafeUserInput) // compiler rejects passing this where
render(html: SafeHtml) // SafeHtml is requiredgo deeper
Define it with an example (strName, iCount) and give the core reason: the compiler and IDE already show the type, so the prefix is noise that can go stale.
Add that the prefix becomes an active lie after a type change, that it hurts scanning and autocomplete, and that units-in-names and casing conventions are a different, legitimate thing.
Bring in the Systems vs Apps Hungarian distinction and the modern replacement — promote a safety-relevant semantic distinction into the type system so the compiler enforces it.
Position it as a policy question: encode only information tooling cannot supply, standardise the surviving conventions per ecosystem in a style guide and linter, and treat safety-critical distinctions (unsafe input, currency, units, time zone) as candidates for dedicated types rather than naming discipline.
## What it is **Hungarian notation** is a naming convention in which a short prefix encodes something about the identifier that the language itself already knows or could know: - **Type prefixes** — `strName` (string), `iCount` (int), `bReady` (bool), `arrItems` (array), `lpszBuffer` (long pointer to a zero-terminated string). - **Scope / storage prefixes** — `m_total` (member field), `g_config` (global), `s_instance` (static), `_id` (private), `p_` (parameter). - **Kind prefixes/suffixes on types** — `IShape` (interface), `ShapeImpl` / `CShape` (implementation/class), `EColor` (enum). It is named after Charles Simonyi, who was Hungarian, and it spread through Microsoft's Windows and Visual Basic APIs in the 1980s–90s. ## The two Hungarians Joel Spolsky's well-known distinction is worth knowing because it is the strongest counter-argument in the interview: - **Systems Hungarian** encodes the *machine type*: `str`, `i`, `dw`. This is the version everyone criticises. It duplicates what a static type system already guarantees. - **Apps Hungarian** encoded the *semantic kind* — the original intent. The classic example is `usName` (unsafe, unescaped, from the user) vs `sName` (safe, already HTML-escaped). Mixing them is a cross-site-scripting bug, and the prefixes made the bug visible at the point of assignment. Apps Hungarian addressed a real problem. But the modern answer to that problem is a **distinct type** — `UnsafeInput` and `SafeHtml` as separate types, so the *compiler* rejects the mix instead of relying on a human noticing a two-letter prefix. That is the key move to articulate: the good idea in Hungarian notation gets promoted from a naming convention into the type system. ## Why systems-style encodings are discouraged today 1. **Redundant.** In a statically typed language the compiler knows and enforces the type; the IDE shows it on hover, in completion, and in the signature. In a dynamically typed language the prefix isn't enforced either, so it is a promise nobody checks. 2. **It rots into disinformation.** Change `strPhone` from a string to a value object and every use now claims something false. The rename is mechanical but nothing *forces* it, so in practice codebases end up full of `strFoo` that are no longer strings — a name that actively misleads is worse than a vague one. 3. **It damages scanning and completion.** When two hundred identifiers begin with `m_` or `str`, the distinguishing part of the word is pushed right, defeating the eye's habit of reading prefixes and defeating prefix-based autocomplete. 4. **It crowds out meaning.** The characters spent on `lpsz` are characters not spent saying *what the value is for*. 5. **Refactoring friction.** Extract a member into a local, or a local into a parameter, and a scope-encoding convention demands a rename for a change that otherwise has no semantic content. ## What survives, and why Not every encoding is wrong; several persist as *ecosystem convention*, and a candidate who declares them all forbidden is over-applying the rule: - **Leading underscore for private fields** (`_id`) — widespread in C#, JavaScript/TypeScript, and Python (where `_` genuinely means "internal" and `__` triggers real name mangling). It disambiguates constructor parameters from fields where the language lacks a cleaner way to do so. - **`I` prefix for interfaces** — the norm in C#/.NET, discouraged in Java, Kotlin, Go, and Swift style guides. Follow the local guide; do not import one ecosystem's rule into another. - **Constants in `UPPER_SNAKE_CASE`**, types in `PascalCase`, members in `camelCase` — these encode *kind*, not type, are enforced by linters, and aid reading rather than hindering it. - **Units in names** — `timeoutMillis`, `weightKg`, `priceInCents`. This is not type encoding; it is semantic information the type usually does *not* carry (an `int` does not say milliseconds). Stronger still is a `Duration` or `Money` type that makes the unit unforgeable. - **Meaningful qualifiers on implementations** — `JdbcOrderRepository`, `InMemoryCache`, `RetryingHttpClient`. These say *how*, which is real information, unlike `OrderRepositoryImpl`. ## The underlying principle Encode **meaning that the reader cannot otherwise obtain**; do not encode **facts the compiler and the tooling already state**. And when a naming prefix is doing safety-critical work (safe vs unsafe strings, cents vs dollars, UTC vs local time), promote it out of the name and into a type, where it is checked rather than merely suggested.
- Is there any version of Hungarian notation you would defend?Apps Hungarian — encoding *semantic kind* such as unsafe-vs-escaped strings — solved a real bug class. But the modern form of that idea is separate types (`UnsafeInput` vs `SafeHtml`), so the compiler enforces the distinction instead of a human spotting a prefix.
- If type prefixes are bad, why is `timeoutMillis` good?Because the unit is semantic information the type does *not* carry — an integer cannot tell you milliseconds from seconds. It documents meaning rather than duplicating a compiler-checked fact. Stronger again is a `Duration` type that makes the unit impossible to get wrong.
- Would you ban the `_` private-field prefix or the `I` interface prefix?No — those are ecosystem conventions, not type encodings, and consistency with the local style guide beats importing a rule from another language community. `_field` is idiomatic in C#/TypeScript/Python; `IShape` is idiomatic in C# and unidiomatic in Java, Kotlin, and Go.
Writing strName is like labelling a jar "GLASS JAR: jam". The material is obvious by looking, it becomes wrong the day you switch to a plastic tub, and it wastes the label space that should have said which jam.
saying these in an interview costs you the question
- "Prefixes make the code more explicit" — they duplicate compiler-checked facts with unchecked text.
- Treating all prefixes as Hungarian, including linter-enforced casing conventions and unit suffixes.
- Not knowing the Systems vs Apps Hungarian distinction, or dismissing the Apps case outright.
- Adding `Impl` as the default implementation suffix instead of a qualifier that says *how*.
- Importing one ecosystem's convention (e.g. `IShape`) into a language whose style guide rejects it.