Variable Substitution Techniques
f-strings, .format(), and template libraries for prompt rendering.
Variable Substitution Techniques is a free AI Prompt Engineering lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AI Prompt Engineering learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Four Python Approaches to Template Rendering
Python offers several ways to render prompt templates with variable substitution. Each has strengths and trade-offs:
- f-strings — inline, immediate, no imports needed
- str.format() — named placeholders, validation-friendly
- string.Template — safe dollar-sign substitution, partial filling supported
- Jinja2 — full templating engine: conditionals, loops, filters, inheritance
Choosing the right approach depends on template complexity, team skills, and whether you need advanced features like conditionals and loops.
Approach 1: Python f-strings
F-strings are the simplest approach for prompt templates where all variables are available at render time:
import openai
client = openai.OpenAI(api_key='sk-...')
def generate_linkedin_post(company, topic, tone, word_count):
prompt = (
f'Write a LinkedIn post for {company} about {topic}. '
f'Tone: {tone}. '
f'Length: {word_count} words. '
'Professional but conversational. '
'End with one question to engage readers. '
'No hashtags. Active voice.'
)
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}]
)
return response.choices[0].message.content
print(generate_linkedin_post(
company='DataStream Analytics',
topic='how AI is changing data pipelines',
tone='enthusiastic but grounded',
word_count=180
))Approach 2: str.format()
str.format() works well when you want to store template strings separately from the code that fills them in — useful for loading templates from files:
import openai
client = openai.OpenAI(api_key='sk-...')
# Template stored as a module-level constant or loaded from a file
SUPPORT_REPLY_TEMPLATE = '''You are a customer support agent for {company_name}.
Respond to this customer message:
---
{customer_message}
---
Tone: {tone}.
Keep the response under {max_words} words.
Do not offer refunds unless the customer explicitly asks.
Always close by asking if there is anything else you can help with.'''
def generate_support_reply(company, message, tone='empathetic and helpful', max_words=150):
prompt = SUPPORT_REPLY_TEMPLATE.format(
company_name=company,
customer_message=message,
tone=tone,
max_words=max_words
)
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}]
)
return response.choices[0].message.contentApproach 3: string.Template
string.Template from Python's standard library uses dollar-sign placeholders ($variable or ${variable}). Its key advantage: safe_substitute() leaves missing variables as literal placeholder text rather than raising an error, enabling partial fills:
from string import Template
import openai
client = openai.OpenAI(api_key='sk-...')
# $ placeholders — safe with code that contains curly braces
BASE_TEMPLATE = Template(
'Write a $format_type for $audience about $topic. '
'Tone: $tone. Length: $word_count words. '
'Active voice. No jargon.'
)
def generate(format_type, audience, topic, tone='professional', word_count=200):
prompt = BASE_TEMPLATE.substitute(
format_type=format_type,
audience=audience,
topic=topic,
tone=tone,
word_count=word_count
)
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}]
)
return response.choices[0].message.content
# Partial fill example — safe_substitute leaves $word_count as-is
partial = BASE_TEMPLATE.safe_substitute(
format_type='blog post', audience='developers', topic='API design'
)
print(partial) # $tone and $word_count remain as placeholdersApproach 4: Jinja2 Basics
Jinja2 is a full templating engine. It supports conditionals, loops, filters, and template inheritance — far beyond simple string substitution:
from jinja2 import Template
import openai
client = openai.OpenAI(api_key='sk-...')
# Jinja2 uses {{ }} for variables and {% %} for logic
JINJA_PROMPT = Template('''
Write a {{content_type}} for {{audience}} about {{topic}}.
Tone: {{tone}}.
{% if include_examples %}
Include {{example_count}} concrete examples.
{% endif %}
{% if word_count %}
Length: {{word_count}} words.
{% else %}
Aim for 200-300 words.
{% endif %}
Active voice. No jargon.
''')
def generate(content_type, audience, topic, tone, include_examples=False, example_count=2, word_count=None):
prompt = JINJA_PROMPT.render(
content_type=content_type,
audience=audience,
topic=topic,
tone=tone,
include_examples=include_examples,
example_count=example_count,
word_count=word_count
)
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}]
)
return response.choices[0].message.contentJinja2 Loops in Templates
Jinja2 loops let you iterate over lists in your template — useful for generating multi-item prompts from data structures:
from jinja2 import Template
import openai
client = openai.OpenAI(api_key='sk-...')
MULTI_PRODUCT_TEMPLATE = Template('''
Write a product comparison for {{audience}}.
Compare the following products:
{% for product in products %}
- {{product.name}}: {{product.description}}
{% endfor %}
Structure: one paragraph per product, then a 2-sentence recommendation.
Tone: {{tone}}. Active voice. No bullet points in paragraphs.
''')
products = [
{'name': 'Asana', 'description': 'project management with timeline views'},
{'name': 'Linear', 'description': 'developer-focused issue tracking'},
{'name': 'Monday.com', 'description': 'visual work management for teams'}
]
prompt = MULTI_PRODUCT_TEMPLATE.render(
audience='startup founders',
products=products,
tone='direct and practical'
)
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}]
)
print(response.choices[0].message.content)Jinja2 Filters
Jinja2 filters transform variable values inline during template rendering. Built-in filters useful for prompts:
{{ topic | upper }}— uppercase the topic{{ word_count | default(200) }}— use 200 if word_count is not provided{{ audience | title }}— title-case the audience string{{ items | join(', ') }}— join a list with commas
Filters keep transformation logic inside the template rather than in the Python code that calls it, making templates more self-contained and portable.
Loading Templates from Files
For large or complex templates, storing them in separate text files keeps your Python code clean. Jinja2's Environment and FileSystemLoader handle this well:
from jinja2 import Environment, FileSystemLoader
import openai
client = openai.OpenAI(api_key='sk-...')
# Load all templates from the 'prompts/' directory
env = Environment(loader=FileSystemLoader('prompts/'))
def render_template(template_name, variables):
'''Load and render a .j2 template file with the given variables.'''
template = env.get_template(template_name)
return template.render(**variables)
def generate_from_file(template_name, variables):
prompt = render_template(template_name, variables)
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}]
)
return response.choices[0].message.content
# Usage: load prompts/blog_post.j2 and fill with variables
result = generate_from_file('blog_post.j2', {
'topic': 'API rate limiting strategies',
'audience': 'backend engineers',
'tone': 'technical and direct',
'word_count': 500
})
print(result)Choosing the Right Approach
Match the substitution approach to the complexity of your templates:
- f-strings — quick scripts, one-off automation, templates short enough to read inline
- str.format() — stored templates, team codebases, when KeyError on missing vars is desirable
- string.Template — when content might contain curly braces (code snippets), or when partial filling is needed
- Jinja2 — complex templates with conditionals, loops, multiple files, or a team with templating experience
Over-engineering is a real risk — reach for Jinja2 only when you genuinely need its advanced features.
Template Safety: Injection Attacks
When variable values come from user input, prompt injection is a real risk. A malicious user might provide a value like: "Ignore all previous instructions and..."
Defensive measures:
- Validate and sanitize all user-provided variables before substitution
- For user-facing inputs, wrap the variable in delimiters: "The user input is: ---{user_input}---"
- Use output filtering to detect and reject responses that look like they followed injected instructions
- Never give user-provided values access to system prompt variables
Testing Template Renders
Always test template renders separately from API calls. Validate the rendered string before sending it to the model:
def test_template_render():
test_cases = [
{'topic': 'cloud security', 'audience': 'CTOs', 'tone': 'formal', 'word_count': 300},
{'topic': 'ML pipelines', 'audience': 'data scientists', 'tone': 'technical', 'word_count': 500},
# Edge cases
{'topic': '', 'audience': 'developers', 'tone': 'casual', 'word_count': 100}, # empty topic
{'topic': 'AI' * 100, 'audience': 'all', 'tone': 'brief', 'word_count': 50}, # very long topic
]
TEMPLATE = 'Write a {word_count}-word {tone} article about {topic} for {audience}. Active voice.'
for i, case in enumerate(test_cases):
try:
rendered = TEMPLATE.format(**case)
assert len(rendered) > 0, 'Empty render'
print(f'Case {i+1} OK: {len(rendered)} chars')
except (KeyError, AssertionError) as e:
print(f'Case {i+1} FAILED: {e}')
test_template_render()Knowledge Check: Substitution Techniques
You are building a prompt system where template files are stored on disk, templates include conditional sections (e.g., optionally include a section about pricing based on a flag), and templates may be contributed by multiple team members who are familiar with web templating.
Which substitution approach is best suited for this scenario?
Recap: Variable Substitution Techniques
Python offers four approaches to prompt template rendering: f-strings (inline, simple), str.format() (named placeholders, KeyError on missing vars), string.Template (dollar-sign syntax, safe partial fills), and Jinja2 (full engine with conditionals, loops, filters, and file loading).
Match the approach to the complexity: use f-strings for quick scripts, str.format() for stored templates, string.Template when content contains curly braces, and Jinja2 when you need conditionals, loops, or file-based templates. Always test renders separately from API calls.
Frequently asked questions
Is the “Variable Substitution Techniques” lesson free?
Yes — the full text of “Variable Substitution Techniques” is free to read here on the web, and the AI Prompt Engineering course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AI Prompt Engineering course, upgrade to CoddyKit PRO.
What will I learn in “Variable Substitution Techniques”?
f-strings, .format(), and template libraries for prompt rendering. You practise AI Prompt Engineering with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start AI Prompt Engineering?
No prior experience is required. AI Prompt Engineering on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Variable Substitution Techniques” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this AI Prompt Engineering lesson?
Yes. Every AI Prompt Engineering lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- What Is a Prompt Template?
- Creating Fill-in-the-Blank Patterns
- Variable Substitution Techniques
- Reusing Templates Across Tasks