Template Syntax
Core syntax: {{variable}} for variable substitution, {{#if condition}}...{{/if}} for conditionals, {{#each items}}...{{/each}} for loops, {{> partial_name}} for partials, and {{block name}}...{{/block}} for template inheritance blocks.
Data Model
Template (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL,
version INT NOT NULL,
source TEXT NOT NULL,
compiled_bytecode BYTEA,
created_at TIMESTAMP DEFAULT NOW()
)
Compilation Pipeline
Parse source into an AST, then generate bytecode or target-language code (e.g., Python) from the AST, and store the compiled form in compiled_bytecode. On rendering, load the compiled template and execute it with the provided context dict, avoiding re-parse overhead.
Template Inheritance
A base template defines named blocks via {{block name}}...{{/block}}. A child template declares {{extends base_template}} and overrides individual blocks. Blocks not overridden fall back to the base definition. Partials are reusable sub-templates included via {{> partial_name}}.
Context Sandboxing
Restrict attribute access to an explicit whitelist. Prevent access to dangerous attributes such as __class__, __globals__, __subclasses__. Evaluate expressions in a restricted environment — no built-in eval, no file I/O, no subprocess execution. Jinja2 SandboxedEnvironment is a reference implementation.
Caching
Compiled templates are cached in Redis keyed by name:version. LRU eviction policy limits memory usage. On template update, the version is incremented and the old Redis key is deleted to invalidate the cache. Hot reload is automatic: next render fetches and re-caches the new compiled version.
Multi-Tenancy
Templates are scoped by tenant_id. Lookup resolves tenant-specific override first, then falls back to the default (global) template. This allows tenants to customize templates without forking the base.
Rendering Metrics
Track render time per template name using a histogram (e.g., Prometheus). Alert on p99 render latency exceeding threshold. Metrics help identify slow templates caused by complex loops or excessive partial inclusion.
{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “What is the difference between a template engine and string concatenation?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “A template engine provides structured syntax (variables, conditionals, loops, inheritance) with context sandboxing and caching. String concatenation is error-prone, vulnerable to injection, and lacks reuse primitives. Template engines separate presentation logic from application code and compile templates for repeated fast rendering.”
}
},
{
“@type”: “Question”,
“name”: “How does template inheritance work in a template engine?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “A base template defines named blocks using block tags. Child templates declare that they extend the base and override individual blocks. At render time the engine merges child block content into the base layout, falling back to the base block's default content for any block the child does not override.”
}
},
{
“@type”: “Question”,
“name”: “Why is context sandboxing important in a template engine?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Without sandboxing, a malicious template could access Python internals such as __class__ or __globals__ and execute arbitrary code or read sensitive data. Sandboxing restricts the execution environment to a whitelist of safe attributes and operations, preventing template authors from breaking out of the rendering context.”
}
},
{
“@type”: “Question”,
“name”: “How do you cache compiled templates efficiently?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Store the compiled bytecode or generated code in Redis keyed by template name and version. Use an LRU eviction policy to bound memory. When a template is updated, increment its version number and delete the old Redis key so the next render fetches and stores the fresh compiled form automatically.”
}
}
]
}
See also: Atlassian Interview Guide
See also: Shopify Interview Guide