In TypeScript, what values does the type `type Greeting = `hello ${string}`` accept, and how is it different from plain `string`?
answer
- a pattern, not a single string
- the hole accepts anything, including empty
- subtype of string, assignment one-way
- how the string was built decides the type
- nothing is checked after compilation
basics
~20 sAny string that begins with "hello " — the placeholder matches any string, so "hello world" is assignable but "hi" is not. The type is a narrower subtype of string, and the match is checked only at compile time.
solid answer
~50 sA template literal type is a *pattern* over strings, written with the same `${}` syntax as a template string but in type position. `` `hello ${string}` `` describes every string that starts with the literal text `hello ` followed by anything at all, so `"hello world"` and `"hello "` are assignable while `"hi"` and `"Hello world"` are not. It is strictly narrower than `string`: assignment flows one way, so you can pass a `` `hello ${string}` `` where a `string` is wanted, but a value declared as plain `string` is rejected where the pattern is wanted, because the compiler cannot prove the prefix. Placeholders are not limited to `string` — `number`, `bigint`, `boolean`, `null`, `undefined`, literal types and unions of them are all allowed. And like everything else in the type layer it is erased: the emitted JavaScript contains no prefix check, so the guarantee holds only for values the checker actually saw.
go deeper
Be able to read `hello ${string}` aloud as "any string starting with hello space" and say that it is narrower than string. Knowing that the hole accepts anything, including the empty string, is the main recall point.
Explain the assignability direction in both directions and why let widening and + concatenation break the match while a contextually typed template string expression does not. Name which types are legal in a placeholder.
Show where the guarantee ends: strings arriving from JSON, environment variables or the DOM are plain string, and forcing them in with an assertion buys nothing. Describe the boundary check you would write instead.
Frame it as a design choice about where structured strings should be validated. Argue when encoding a string's shape in the type layer pays for itself versus when a branded type or a parsed domain object is the better contract across teams.
## The idea A template literal type looks exactly like a template string, but it lives in type position and describes a *set of strings* rather than producing one: ```ts type Greeting = `hello ${string}`; ``` Read it as a pattern: the literal text `hello ` followed by a hole that any string may fill. The compiler uses it purely as an assignability rule — given a candidate type, does it match the pattern? ## What is assignable ```ts const a: Greeting = "hello world"; // ok const b: Greeting = "hello "; // ok — the hole may be empty const c: Greeting = "hello, world"; // ok — the hole may contain anything const d: Greeting = "Hello world"; // error — patterns are case-sensitive const e: Greeting = "hi"; // error — prefix missing ``` The matching is literal and case-sensitive. The hole is unconstrained: it can be empty, contain spaces, punctuation, newlines, anything. A common wrong instinct is to read `${string}` as "one word" or "no slashes"; it means *any string whatsoever*. ## It is a subtype, and assignment is one-way `` `hello ${string}` `` is a subtype of `string`. That direction works: ```ts declare let g: Greeting; const s: string = g; // ok — every Greeting is a string ``` The reverse does not: ```ts declare let raw: string; const g2: Greeting = raw; // error — string is not narrow enough ``` This is the same relationship a string literal type has to `string`: `"a"` is assignable to `string`, `string` is not assignable to `"a"`. A template literal type just describes a bigger, pattern-shaped subset instead of a single value. ## Where the literal type comes from matters Because the check runs on *types*, not values, how a string reaches the call site decides whether it compiles: ```ts declare function greet(g: `hello ${string}`): void; const name = "world"; // inferred as the literal type "world" greet(`hello ${name}`); // ok greet("hello " + name); // error — `+` on strings produces `string` let mutable = "hello world"; // `let` widens to string greet(mutable); // error ``` Two mechanics are visible here. First, a template *string expression* that is contextually typed by a template literal type is itself given a template literal type, so `` `hello ${name}` `` checks. Second, ordinary string concatenation with `+` produces plain `string`, and `let` bindings widen their literal type to `string`, so both lose the information the pattern needs. ## What goes in the hole The placeholder is not restricted to `string`. `number`, `bigint`, `boolean`, `null`, `undefined`, string/number literal types, unions of literals, and other template literal types are all legal: ```ts type Version = `v${number}`; const v1: Version = "v2"; // ok const v2: Version = "v2.1"; // ok — a decimal is still a number literal const v3: Version = "vNext"; // error — does not look like a number ``` `` `${number}` `` accepts strings whose text is shaped like a numeric literal. That is a syntactic check on the string, not arithmetic: nothing is parsed at runtime. ## Types are erased — the guarantee has an edge Nothing survives compilation. `type Greeting = ...` emits no JavaScript, and `greet(x)` emits a bare call with no prefix test. So the pattern constrains code the checker can see and nothing else. A string that arrives from `JSON.parse`, a network response, `process.env`, or a DOM input is typed `string` (or `any`), and pushing it into a template-literal-typed slot needs either a real runtime check or an assertion. An assertion here is a promise, not a verification: ```ts function isGreeting(s: string): s is Greeting { return s.startsWith("hello "); // a real check you wrote yourself } ``` ## When to reach for it The pattern earns its keep where strings carry structure that used to be documented only in a comment: route paths, CSS custom properties, event names, prefixed keys, semantic-version strings. It gives autocomplete and a red squiggle at the point where the string is *built*, which is where the mistake actually happens. It is not a validator, and treating it as one is the mistake to avoid.
- Which types are legal inside the `${}` of a template literal type?`string`, `number`, `bigint`, `boolean`, `null` and `undefined`, plus string and number literal types, unions of those, and other template literal types. `` `${number}` `` accepts strings that look like numeric literals — `"42"` and `"2.5"` match, `"vNext"` does not. Object and function types are not allowed in a placeholder.
- Why does `'hello ' + name` fail where `` `hello ${name}` `` succeeds for the same parameter?String concatenation with `+` always produces plain `string`, which is too wide for the pattern. A template *string expression* behaves differently: when its contextual type is a template literal type, the compiler infers a template literal type for it instead of widening to `string`, so the pattern matches.
- Does declaring a parameter as `` `hello ${string}` `` stop a bad string reaching the function at runtime?No. The type is erased at compile time, so the emitted function has no prefix check. It constrains only call sites the checker analysed with sufficiently precise types. Anything crossing a runtime boundary — JSON, form input, environment variables — needs a real check, such as a type predicate that calls `startsWith`.
saying these in an interview costs you the question
- Says a value typed string is assignable to the pattern
- Claims the compiler emits a runtime check for the prefix
- Reads ${string} as matching one word only
- Thinks the pattern matching is case-insensitive
- Confuses the type with a runtime template string