| name | hejlsberg-language-design |
| description | Design languages and type systems in the style of Anders Hejlsberg, creator of Turbo Pascal, Delphi, C#, and TypeScript. Emphasizes practical type systems, developer productivity, gradual typing, and IDE-driven language design. Use when designing languages, type systems, or developer tools. |
| tags | typescript, language-design, type-system, c#, turbo-pascal, delphi, generics, ide, tooling, developer-experience |
Anders Hejlsberg Style Guide
Overview
Anders Hejlsberg created Turbo Pascal (the fastest compiler of its era), Delphi (RAD with native compilation), C# (managed language with modern features), and TypeScript (typed JavaScript at scale). His career spans four decades of making developers more productive through language and tooling innovation.
Core Philosophy
"A language is only as good as its tooling."
"Types should help you, not get in your way."
"The best type system is one that understands existing code."
Hejlsberg believes languages exist to serve developers—making them more productive, catching their errors, and enabling great tooling.
Design Principles
-
Developer Productivity First: Every feature should make developers faster.
-
Gradual Adoption: Meet developers where they are.
-
IDE-Driven Design: Consider tooling from day one.
-
Pragmatic Type Systems: Types that work with real code, not against it.
When Designing Languages
Always
- Consider the IDE experience for every feature
- Provide escape hatches for edge cases
- Enable gradual adoption of type safety
- Make common patterns easy
- Design for tooling (completion, refactoring, navigation)
- Maintain backward compatibility
Never
- Sacrifice developer experience for type purity
- Require full type coverage from day one
- Break existing code without migration paths
- Design features that can't be tooled
- Ignore the ecosystem of existing code
- Make simple things verbose
Prefer
- Structural typing over nominal (for flexibility)
- Type inference over explicit annotations
- Gradual typing over all-or-nothing
- Composition over inheritance
- Async/await over callbacks
- Null safety without verbosity
Code Patterns
Structural Typing (TypeScript)
interface Point {
x: number;
y: number;
}
function distance(p1: Point, p2: Point): number {
return Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2);
}
const a = { x: 0, y: 0 };
const b = { x: 3, y: 4, label: "target" };
distance(a, b);
Union Types and Type Guards
type Result<T> =
| { success: true; value: T }
| { success: false; error: string };
function process<T>(result: Result<T>): T | null {
if (result.success) {
return result.value;
} else {
console.error(result.error);
return null;
}
}
type State =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: string }
| { status: "error"; message: string };
function render(state: State): string {
switch (state.status) {
: ;
: ;
: state.;
: state.;
}
}
Generics with Constraints
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map(item => item[key]);
}
const users = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 }
];
const names = pluck(users, "name");
const ages = pluck(users, "age");
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};
type Required<T> = {
[P in keyof T]-?: T[P];
};
type NonNullable<T> = T extends null | undefined ? never : T;
type ReturnType<T> = T extends (...: []) => infer R ? R : ;
Async/Await (C# Innovation)
public async Task<User> GetUserWithOrdersAsync(int userId)
{
var user = await _userRepository.GetByIdAsync(userId);
if (user == null)
return null;
var ordersTask = _orderRepository.GetByUserAsync(userId);
var preferencesTask = _preferenceRepository.GetAsync(userId);
await Task.WhenAll(ordersTask, preferencesTask);
user.Orders = ordersTask.Result;
user.Preferences = preferencesTask.Result;
return user;
}
LINQ: Language-Integrated Query
var results = from user in users
where user.Age >= 18
orderby user.Name
select new { user.Name, user.Email };
var results = users
.Where(u => u.Age >= 18)
.OrderBy(u => u.Name)
.Select(u => new { u.Name, u.Email });
var dbResults = from order in db.Orders
join customer in db.Customers
on order.CustomerId equals customer.Id
where order.Total > 1000
select new { customer.Name, order.Total };
Null Safety Without Pain
#nullable enable
public class UserService
{
public string GetDisplayName(User user)
{
return user.Name;
}
public User? FindUser(string email)
{
return _repository.FindByEmail(email);
}
public void ProcessUser(string email)
{
var user = FindUser(email);
if (user != null)
{
Console.WriteLine(user.Name);
}
var name = user?.Name ?? "Unknown";
}
}
Pattern Matching Evolution
if (obj is string s)
{
Console.WriteLine(s.Length);
}
var description = shape switch
{
Circle { Radius: 0 } => "Point",
Circle { Radius: var r } => $"Circle with radius {r}",
Rectangle { Width: var w, Height: var h } when w == h => $"Square {w}x{w}",
Rectangle { Width: var w, Height: var h } => $"Rectangle {w}x{h}",
_ => "Unknown shape"
};
var result = numbers switch
{
[] => "Empty",
[var single] => $"Single: {single}",
[var first, .., var last] => $"First: {first}, Last: {last}",
_ => "Other"
};
Type Inference Done Right
const message = "hello";
const count = 42;
const items = [1, 2, 3];
const doubled = items.map(x => x * 2);
function createUser(name: string, age: number) {
return { name, age, createdAt: new Date() };
}
function first<T>(items: T[]): T | undefined {
return items[0];
}
const n = first([1, 2, 3]);
const s = first(["a", "b"]);
IDE-First Feature Design
interface UserService {
getUser(id: string): Promise<User>;
saveUser(user: User): Promise<void>;
}
class OrderProcessor {
processOrder(order: Order) {
this.validate(order);
this.save(order);
}
}
type Status = "pending" | "active" | "completed";
const status: Status = "|"
const config = {
name: "app",
untyped: someValue,
};
Language Evolution Philosophy
Gradual Typing Adoption Path
══════════════════════════════════════════════════════════════
Phase Type Coverage Developer Action
────────────────────────────────────────────────────────────
1. Baseline 0% Rename .js → .ts, compiles!
2. Implicit 20% Add types to public APIs
3. Strict 60% Enable strict null checks
4. Complete 90%+ Full coverage, all strict
Key insight: Each phase provides value
No phase requires rewriting code
Migration can be file-by-file
Mental Model
Hejlsberg approaches language design by asking:
- What's the developer experience? Features must be usable
- How does this work in the IDE? Tooling is part of the design
- Can this be adopted gradually? Don't require big-bang rewrites
- Does this match real code patterns? Type systems should model reality
- Is there an escape hatch? Sometimes types are too strict
Signature Hejlsberg Moves
- Turbo Pascal speed: Compilation so fast it changed expectations
- Delphi's RAD: Visual design with native performance
- C# async/await: Made async mainstream and readable
- LINQ: Query syntax integrated into the language
- TypeScript's structural typing: Types for JavaScript at scale
- Gradual typing: Adopt at your own pace
- Nullable reference types: Null safety without rewrites
- Mapped/conditional types: Type-level computation