Before frameworks, "put data into a string" meant a template engine — Mustache, Handlebars, EJS. The simplest useful version interpolates {{ variable }} placeholders with values from a data object, and supports dotted paths ({{ user.name }}) for nested lookups. It's a small but instructive mix of regex, path resolution, and safe defaults.
Implement renderTemplate(template, data). Replace each {{ path }} with the value at path in data (dotted paths walk nested objects), tolerate whitespace inside the braces, stringify non-strings, and turn any missing value into an empty string — without throwing.
function renderTemplate(template, data) {
// returns the template with {{ path }} placeholders filled in
}
renderTemplate('Hello {{name}}', { name: 'Ada' }); // 'Hello Ada'
renderTemplate('{{user.name}}', { user: { name: 'Ada' } }); // 'Ada'
renderTemplate('Hi {{missing}}!', {}); // 'Hi !' (empty, no throw)
renderTemplate('count: {{n}}', { n: 0 }); // 'count: 0' (0 is present)
/\{\{\s*([\w.]+)\s*\}\}/g: the \s* absorbs whitespace, [\w.]+ captures a dotted path, g replaces all.path.split('.').reduce((obj, key) => obj?.[key], data) walks nested objects and safely yields undefined if any step is missing.undefined/null as ''; but 0, false, and '' are present values (use == null, not falsiness).String(value) coerces numbers/booleans.Unlock the solution & editor
Runnable editor + tests
Solve in the browser with instant Jest feedback.
Detailed solutions
Walkthroughs, edge cases, and complexity notes.
Multi-framework variants
React, Vue, Vanilla, Angular — same question, different stacks.