Expand Recursive Key-Value Placeholders in a Template
GoogleSoftware Engineer · Technical ScreenLast updated
Implement a template engine that recursively resolves variable placeholders using a key-value dictionary while handling circular dependencies, missing keys, and escaped characters.
Problem Statement
You are given a template string template and a dictionary of key-value pairs mapping. Your goal is to resolve and replace all placeholders in template with their corresponding expanded values.
Placeholders follow these rules:
- A placeholder is denoted by
%KEY%, whereKEYconsists of one or more alphanumeric characters or underscores ([A-Za-z0-9_]+). - Values in
mappingmay themselves contain placeholders (e.g.,%ANOTHER_KEY%), which must be recursively expanded. - Two consecutive percent characters
%%denote an escaped literal%character and should not be parsed as placeholder delimiters. - If a circular dependency is detected among keys during expansion (e.g.,
Arefers toBandBrefers toA), raise aValueError. - If a placeholder references a key that does not exist in
mapping, raise aKeyError.
Return the fully expanded string.
Function Signature
def expand_template(template: str, mapping: dict[str, str]) -> str:
pass
Constraints
0 <= len(template) <= 10^40 <= len(mapping) <= 1000- Keys in
mappingconsist of[A-Za-z0-9_]+ - All strings consist of printable ASCII characters
Examples
Example 1
template = "I am %USER%. My home is %HOME%."
mapping = {
"USER": "admin",
"HOME": "/%USER%/home"
}
output = expand_template(template, mapping)
# Result: "I am admin. My home is /admin/home."
Explanation: %USER% expands to "admin". %HOME% expands to "/%USER%/home", which recursively resolves to "/admin/home".
Example 2
template = "Usage: 100%% completed by %USER%"
mapping = {
"USER": "alice"
}
output = expand_template(template, mapping)
# Result: "Usage: 100% completed by alice"
Explanation: %% is escaped to a single literal %.
Example 3 (Cycle)
template = "Config: %A%"
mapping = {
"A": "%B%",
"B": "%A%"
}
# Raises ValueError: Circular dependency detected
Edge Cases to Consider
- Unmatched or single
%characters that do not form a valid placeholder should be treated as literal text. - Deeply nested or chain-like expansions (e.g.,
A -> B -> C -> ... -> Z). - Repeated references to the same key; expansion should avoid redundant work through memoization.
Hints
Hint 1
Treat the keys in mapping as nodes in a directed dependency graph and use a three-color depth-first search (unvisited, visiting, visited) to detect cycles.
Hint 2
Memoize the fully resolved string for each key so you only resolve each entry in mapping at most once.
Hint 3
Handle escaping (`%%`) carefully during tokenization or parsing so it does not interfere with the detection of placeholder boundaries.
Follow-up questions
- How would you adapt your solution if the template is an unbounded stream of text rather than an in-memory string?
- How would you support default fallback values if a key is not found (for example, `%KEY:-default%`)?
- If the dependency chain is thousands of levels deep, how would you prevent recursion stack overflow errors?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.