0106 · TrimLeft
0106 · TrimLeft
Problem
Implement TrimLeft<T> so it removes whitespace from the start of a string literal type.
type A = TrimLeft<" hello"> // "hello"
type B = TrimLeft<"\n\t world"> // "world"Only the left side should be trimmed. Internal spaces and trailing spaces should remain unchanged.
Solution
type Whitespace = " " | "\n" | "\t"
type TrimLeft<S extends string> = S extends `${Whitespace}${infer Rest}`
? TrimLeft<Rest>
: SExplanation
This challenge combines two important TypeScript features:
- template literal types
- recursion in conditional types
Step by Step
- Define the set of characters we want to treat as whitespace.
- Check whether the string starts with one of those characters.
- If it does, infer the rest of the string as
Rest. - Recursively call
TrimLeft<Rest>. - If the string does not start with whitespace, stop and return
S.
Why a Whitespace Helper?
type Whitespace = " " | "\n" | "\t"This makes the pattern easier to read and easier to extend. It also mirrors how we think about the problem: "If the string starts with whitespace, remove one character and continue."
Why Recursion Is Necessary
A single match only removes one leading character:
type RemoveOne<S extends string> = S extends `${Whitespace}${infer Rest}`
? Rest
: SThat is not enough for:
type Example = " hello"We need to keep trimming until no leading whitespace remains, so recursion is the correct tool.
Example Walkthrough
type Result = TrimLeft<" \n\thello">This reduces roughly like:
TrimLeft<" \n\thello">
-> TrimLeft<"\n\thello">
-> TrimLeft<"\thello">
-> TrimLeft<"hello">
-> "hello"The recursion stops as soon as the first character is no longer whitespace.
Alternative Solutions
Option 1: Inline the Whitespace Union
type TrimLeft2<S extends string> = S extends `${" " | "\n" | "\t"}${infer Rest}`
? TrimLeft2<Rest>
: SThis behaves the same way, but pulling the union into a named helper usually improves readability.
Option 2: Expand the Pattern Manually
type TrimLeft3<S extends string> = S extends ` ${infer Rest}`
? TrimLeft3<Rest>
: S extends `\n${infer Rest}`
? TrimLeft3<Rest>
: S extends `\t${infer Rest}`
? TrimLeft3<Rest>
: SThis is valid, but it is more repetitive and harder to maintain.
Thought Process
The phrase "remove characters from the start of a string" strongly suggests template literal matching:
S extends `${Something}${infer Rest}`Then the main question becomes: what is Something? In this problem, it is the set of whitespace characters.
After that, the recursive shape is almost automatic:
- match one leading whitespace character
- remove it
- repeat until the match fails
This same pattern shows up again in Trim, Replace, and other string-manipulation challenges.
Key Takeaways
- Template literal types can pattern-match string literal types.
infer Restis useful for "peeling off" part of a string.- Recursive conditional types are a natural fit when a transformation must repeat until no match remains.
Key concepts:
- Template Literal Types
- Conditional Types
- Recursive string processing
