Step-by-step guide On How to design your own programming language with Flare. Learn types, syntax, functions, and compiler basics for beginners.

Why Create a New Language?
Have you ever thought:
“Why does Python care about indentation so much?”
“Why does C++ make me write so many semicolons and type declarations?”
If these thoughts have ever crossed your mind, you’ve already taken the first step toward designing your own programming language.
Building a language isn’t just for experts. It’s for anyone who wants to understand how code works at its deepest level. In this blog post, you’ll learn how to begin your language design journey, and we’ll walk together through building a new programming language from scratch.
We call our language Flare — and you can build your own version alongside us.
Programming Languages: Same Goals, Different Designs
Every language has the same job: turn human instructions into something computers can execute. But how they do it — and how you write in them — varies wildly.
Let’s compare a few examples:
C++
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
Python
print("Hello, World!")
Ruby
puts "Hello, World!"
Rust
fn main() {
println!("Hello, World!");
}
Notice the difference in syntax, verbosity, and structure — and yet they all do the same thing.Before writing any code, you need to wear the hat of a language designer.
Designing a language is about decisions — and those decisions will shape everything else. We’ll walk through each of them step by step, and you’ll make your own choices for the language you’ll build in this series. We’ll also share how we made those same decisions in Flare, our language for this series.
First Things First: Name Your Language
Before making rules or writing syntax, give your language a name.
- Is it fast? Elegant? Friendly?
- Should the name sound serious (like Rust) or playful (like Lua)?
We chose Flare because it evokes speed, simplicity, and brightness — like a small but powerful burst of energy.
By naming your language, you’re shaping its personality.
Key Decisions in Language Design
Now that you have a name, let’s talk about major design choices you’ll need to make. Each of these defines how your language looks, behaves, and feels to use.
We’ll introduce each decision, explain your options, show how popular languages handle them, and reveal the choice we made in Flare.
1. Typing: Static vs Dynamic
What is typing?
Typing defines how your language handles types like int, string, etc. Does the language require the user to define types ahead of time (static), or does it figure them out at runtime (dynamic)?
Static Typing:
- Variables have fixed types.
- Type errors are caught at compile time.
- Example: C++, Java, Rust
int x = 5;
Dynamic Typing:
- Variables can hold any type at any time.
- Errors may occur at runtime.
- Example: Python, JavaScript
x = 5
x = "hello" # still valid
Which should you choose?
- Static typing is easier to implement and leads to better performance.
- Dynamic typing is more flexible and user-friendly, but harder to compile efficiently.
Flare’s decision: Dynamic typing, with optional type hints and type inference. This gives us flexibility while still allowing optimization.
2. Paradigm: Procedural, Functional, or Object-Oriented
What is a paradigm?
It defines how code is structured:
- Procedural languages group instructions into functions (like C).
- Functional languages focus on expressions and immutability (like Haskell).
- Object-Oriented languages organize code into objects and classes (like Java, C++).
Flare’s decision: Procedural. It’s the simplest to understand and implement. No classes, no inheritance.
3. Syntax Style
Do you want your language to use colons and indentation like Python, or braces like C++?
Examples:
- Python:
def hello():
print("Hi")
- C++:
void hello() {
cout << "Hi";
}
Flare’s decision: C-style braces with readable keywords and minimal punctuation.
create hello() {
display "Hi";
}
4. Scoping Rules
Lexical scoping means variables are visible within the block they’re declared in.
Global scoping means variables are accessible everywhere.
Most modern languages use lexical/block scoping.
Flare’s decision: Lexical scoping. It makes reasoning about variables simpler and more predictable.
What Kind of Language Are You Building?
Programming languages serve different purposes:
- Scripting (e.g., Bash, Python)
- Embedded systems (e.g., C)
- Web dev (e.g., JavaScript)
- Academic (e.g., Haskell, Lisp)
- General-purpose (e.g., Python, C++)
In this series, we’re building a general-purpose language — something like Python or C++ that can solve common problems and run standalone.
You can choose your own direction. But whatever your goal, your language must support certain core features.
The Essentials: What Every Language Needs
Let’s explore the essential features that every general-purpose language must have. For each feature, we’ll help you understand:
- What decisions you’ll need to make as the language designer
- How popular languages implement them
- What we’ve chosen for Flare, our language for this series
Remember, every design choice you make will influence how your compiler works. Simpler choices usually mean easier implementation. But don’t be afraid to challenge yourself.
1. Data Types
What You Must Decide:
- What primitive types will your language support?
- Will it allow compound types like lists, dictionaries, sets?
- Will types be statically declared or inferred dynamically?
- How will users declare variables and assign values?
- Will types be enforced at compile time or checked at runtime?
Examples:
- C++: Statically typed. You must declare types explicitly:
int x = 5; - Python: Dynamically typed. Types are inferred:
x = 5 - Python lists can hold mixed types. C++ arrays are fixed and typed.
Flare’s Design:
- Fundamental types: int (Integer), frac (Floating point), flag (Boolean), text (String), letter (Character)
- Compound types: list, dict (key-value pairs)
- Typing model:
- Dynamically typed, but users can optionally give type hints
- Inference is used if no hint is provided
- Dynamically typed, but users can optionally give type hints
- Declaration syntax:
claim x = 42; // int inferred
claim y: text = "hello"; // explicit type hint
You’ll need to implement a way to store type info internally — either inferred from values, or parsed from hints.
2. Declarations and Assignments
What You Must Decide:
- Do variables need to be declared before use?
- Will assignment and declaration use different syntax?
- Are variables mutable, or is immutability the default?
Examples:
- C++:
- Declaration: int x;
- Assignment: x = 10;
- Combined: int x = 10;
- Python:
- Declaration + assignment in one: x = 10
Flare’s Design:
- Variables must be declared using the claim keyword
- Supports both implicit and explicit type declarations
Flare:
claim count = 0; // inferred as int
claim greeting: text = "Hi"; // explicit
claim userlist: list = [1, 2, 3];
3. Conditional Statements
What You Must Decide:
- What syntax will be used for if, else, and elif (or similar)?
- Will you include switch/match constructs?
- Will conditions need to be boolean expressions?
- Will indentation or braces determine scope?
Examples:
- Python:
if x > 0:
print("Positive")
else:
print("Non-positive")
- C++:
if (x > 0) {
std::cout << "Positive";
} else {
std::cout << "Non-positive";
}
Flare’s Design:
- Keywords: check, alt, and block-based scoping using { }
check (score > 90) {
display("A Grade");
} altcheck(score > 50) {
display("Pass");
} alt {
display("Try again");
}
Want to simplify parsing? Avoid using colons/indentation-based blocks like Python. Curly braces ({}) make AST construction easier.
4. Loops
What You Must Decide:
- Will you support while, for, or both?
- Will for loop use range-based or iterator-style syntax?
- Do you need loop control keywords like break and continue?
Examples:
- C++:
for (int i = 0; i < 5; i++) { ... }
while (x < 10) { ... }
- Python:
for i in range(5): ...
while x < 10: ...
Flare’s Design:
- Uses a single loop construct with the during keyword (like while)
- Block required ({}), condition must be a boolean expression
Flare:
claim i = 0;
during i < 5 {
display i;
claim i = i + 1;
}
Simpler to implement than for, but you can always add iterator-based loops later.
5. Functions
What You Must Decide:
- How are functions declared?
- How do parameters and return types work?
- Do you support recursion, default parameters, or overloading?
Examples:
- C++:
int add(int a, int b) {
return a + b;
}
- Python:
def add(a, b):
return a + b
Flare’s Design:
- Functions are declared using the create keyword
- drop is used to return a value
- Supports type hints on parameters and return value
- No overloading or default params yet
Flare:
create add(a, b): int {
drop a+b;
}
Behind the scenes, you’ll store function names, parameter types, and return types in a symbol table.
6. Input/Output and Inbuilt Functions
What You Must Decide:
- How will users print output?
- How will they read input?
- Will you provide utility functions for strings, lists, math, etc.?
Examples:
- Python: print(), input(), len(), append()
- C++: cout, cin, <cmath>, etc.
Flare’s Design:
- Uses display keyword for printing
Flare:
read(name);
display("hello", name);
- Reading input will be added later using a simple read() inbuilt
- Has inbuilt utility functions: len, push, insert, contains etc.
You can internally map these to C++ standard library calls or define your own runtime functions.
Flare — Our Language for This Series
Here’s a quick overview of the design decisions we made for Flare:
Feature | Flare’s Choice |
Typing | Dynamic + type hints |
Scoping | Lexical |
Paradigm | Procedural |
Syntax | C-style blocks, readable keywords |
Data Types | int, frac, flag, text, letter, list, dict |
Loops | loop, during |
Functions | create and drop keywords |
Variable Decl. | claim for inference, type hints optional |
We designed Flare to be:
- Simple
- Practical
- Easy to implement in a compiler
You’ll learn the full internals of how it works as we build it.
What’s Next?
In the next post, we’ll begin writing the lexer — the component that turns code into tokens.
Get ready to:
- Define your language’s tokens
- Build a lexer in C++
- Prepare for parsing and AST construction
This is where your language starts to come alive. See you there!