Who Is the Language For?
Every downstream decision — type system, memory model, concurrency model, execution strategy, error handling — is decided by who is going to write the code and what happens when they get it wrong. Answer this badly and nothing after it can be defended.
Before I decide anything about syntax or types, what question am I supposed to answer first?
No program exists yet. The representation under construction is the language definition itself — a grammar, a static semantics and a dynamic semantics — and the question it exists to answer is which programs are admissible and what each admissible one means. Every later representation in the pipeline is derived from this one, which is why a decision made here cannot be revised by any compiler.
Nothing is transformed here, so the precondition is a design one: a language may only make an assumption about its programs that its audience can actually satisfy, and every guarantee it offers must be enforceable by something — a checker, a runtime, or a proof obligation on the programmer. A guarantee nobody enforces is a documentation comment, and a restriction the audience cannot live with is a language nobody uses. Both failures are unrecoverable later: the type system cannot rescue an audience mismatch, and the compiler cannot enforce a guarantee the semantics never made.
Key points
- The audience decides the type system, the memory model, the concurrency model and the execution strategy, in that causal order — not the other way round.
- The operative facts are: what a wrong program costs, when it is discovered, what the audience will learn, what the program runs on, and what it must interoperate with.
- Every guarantee must be enforceable by a checker, a runtime or an explicit proof obligation; an unenforced guarantee is a comment.
- A restriction the audience will not accept is not a strict language, it is an unused one.
- The answer is permanent in a way no other decision is: JavaScript still carries choices made for a ten-day deadline in 1995.
The question has an answer, and the answer decides the rest
Language design discussions almost always start in the wrong place — significant whitespace, semicolons, whether to have classes — and those are the decisions with the least consequence. The decision with the most consequence is who writes this code, under what pressure, and what it costs when they are wrong. Everything else follows from it, and the following is unusually mechanical.
A language for kernel and embedded work has an audience that cannot accept unpredictable pauses, which rules out tracing garbage collection, which forces either manual management or ownership, which forces either a large class of memory bugs or a borrow checker, which forces a substantially harder learning curve. That is a chain of four forced moves from one audience fact. Nothing about it is a matter of taste.
A language for data analysts has an audience for whom the cost of a wrong program is a wrong number in a notebook, discovered immediately, and for whom the cost of a rejected program is losing a train of thought. That audience wants dynamic types, a REPL, no build step, and a standard library where the interesting operations are one call. It emphatically does not want a borrow checker, and adding one would be a design failure rather than an improvement.
| Audience | What failure costs them | Forced or strongly implied |
|---|---|---|
| Systems and embedded engineers | A pause, a leak or a corruption in something that cannot be restarted | No unpredictable collection; manual or ownership-based memory; predictable layout; ahead-of-time compilation; an escape hatch to raw memory |
| Application and service developers | An incident, caught by tests or by monitoring, fixed by a deploy | Managed memory; strong-enough static types to survive refactoring; fast builds; excellent libraries and interoperability |
| Data analysts and scientists | A wrong number, seen immediately in the next cell | Dynamic types; a REPL; no build step; array and dataframe operations as primitives; interactive error recovery |
| Configuration authors | A misconfigured deploy, which may be the whole outage | No general computation at all; total evaluation; schema validation; readable diffs. See [[configuration-languages]] |
| Domain experts who are not programmers | A rule that silently means something else | Vocabulary from the domain; a small, total language; errors phrased in domain terms; no general-purpose escape hatch. See [[dsl]] |
What the audience actually tells you
The useful form of the question is not a persona but four facts, each of which has a direct consequence in the definition.
- What does a wrong program cost, and when is it discovered? If wrongness costs an incident and is discovered late, move checks earlier and accept rejecting some correct programs. If wrongness costs a rerun and is discovered immediately, static checking buys much less and costs iteration speed. This single question is most of
[[static-vs-dynamic-typing]]. - What can the audience be assumed to know? Ownership, variance and effect types are learnable and expensive to learn. A language whose audience will not pay that cost cannot make them load-bearing, no matter how much they would buy.
- What does the program run on, and for how long? A process that runs for milliseconds cannot afford warmup; one that runs for months can. Hard real-time cannot afford collection pauses. A browser cannot afford native code without a sandbox. Each of these eliminates execution strategies outright —
[[execution-model-choice]]. - What must it talk to? A language that must call existing C libraries has already had large parts of its data representation and error model decided for it, and a language that must run inside an existing runtime has had its object model decided. See
[[interoperability]].
The languages that answered it well, and one that answered it twice
Rust answered "systems engineers who are tired of memory-safety CVEs" and accepted the consequence: a learning curve that is genuinely steep and a borrow checker that rejects correct programs, in exchange for memory safety without a collector. Both halves of that trade are real and the language does not pretend otherwise.
Go answered "large teams at Google building network services, with high turnover" and accepted a different consequence: a deliberately small feature set, fast compilation, and for its first decade no generics at all — a decision widely criticised by people who were not that audience. Fast builds and readability under turnover were the requirement, and the language was shaped by it down to the formatting tool being non-negotiable.
JavaScript answered "make a web page do something, in ten days, in 1995" and got a language shaped entirely by that constraint — implicit coercion, var hoisting, == — which then became the most widely deployed runtime in existence, at which point the original audience answer became a permanent liability. Every subsequent change has had to be backwards compatible with a decision made for a different audience, which is the strongest argument in this lesson for taking the question seriously the first time.
And a language can answer it twice. TypeScript's audience is JavaScript developers on large codebases, which forces a specific and unusual set of decisions: structural typing because JavaScript objects are structural, gradual typing because adoption must be incremental, and full erasure because the output has to be ordinary JavaScript. Nearly every criticism of TypeScript's type system is a criticism of a constraint the audience imposed — see [[gradual-typing]] and [[structural-vs-nominal]].
How it works
The steps, in the order the compiler takes them.
- State the audience concretely, as a role under a pressure, not as a market segment.
- For that audience, write down what a wrong program costs and when it is found; this fixes how much static checking is worth.
- Write down the execution environment and process lifetime; this eliminates execution strategies before any of them is argued for.
- Write down what must be called or embedded; this constrains the data representation and error model before the syntax exists.
- Derive the rest — types, memory, concurrency, tooling — and check each derived decision back against the audience rather than against other languages.
How it breaks
What the engineer observes when it goes wrong — not what goes wrong internally.
- A language acquires a feature because a respected language has it, the feature conflicts with an earlier decision, and the interaction produces a rule nobody can state — the ordinary origin of a language's worst corner.
- A guarantee is documented and not enforced, so users rely on it, and the first implementation that behaves differently is treated as a bug in the implementation rather than in the definition.
- The type system is designed for an audience that will not learn it, adoption stalls, and every subsequent version adds escape hatches that undermine the guarantees the system existed to provide.
- A general-purpose language is used as a configuration format, and a deploy fails because a config file ran a loop, opened a socket or depended on the local clock.
- A language succeeds with an audience it was not designed for, and every later change is constrained by compatibility with decisions that no longer make sense for anyone.
When it helps
- Deciding whether to build a language at all. Most of the time the honest answer to "who is this for" is "people who would be better served by a library" — see
[[should-i-build-a-dsl]]. - Adjudicating a feature request. "Does our audience need this more than they need the simplicity they currently have" is answerable; "is this a good feature" is not.
- Evaluating someone else's language for adoption: reading its audience answer explains most of what looks arbitrary from outside.
When it hurts
- Treating the audience as fixed. Successful languages acquire audiences they were not designed for, and refusing to acknowledge that produces a community fork; accommodating it uncritically produces a language with no coherent centre.
- Using the audience as an argument-stopper. "Our users would not understand it" has been used to reject generics, sum types and pattern matching in languages whose users then reimplemented all three badly.
What it costs
Every one of these is paid by something.
- A narrow audience buys coherence — every feature can be justified against one need, and the language stays small — and costs reach: the language is genuinely unsuitable outside it, and its community will stay proportionally small.
- A broad audience buys adoption and library ecosystem, and costs the ability to make any strong guarantee, because every guarantee excludes someone. Languages that try to serve everyone accumulate escape hatches until the guarantees are advisory.
- Optimising for the audience you have buys immediate usefulness and costs the ability to change: every user is a compatibility constraint, and a language that reached ubiquity before it was finished pays that bill permanently.
What else you could do
What a different compiler or language does instead, and when that is better.
- Do not design a language. A library with a well-chosen API serves most audiences better, costs a fraction as much, and inherits an entire ecosystem's tooling —
[[should-i-build-a-dsl]]. - Design a dialect or a subset of an existing language, inheriting its runtime, its libraries and its editor support, which is what an internal DSL is —
[[internal-vs-external-dsl]]. - Design a data format with a schema instead of a language, if the audience is expressing facts rather than computation. This is almost always the right answer for configuration —
[[configuration-languages]]. - Add the capability to an existing language as a set of types and functions, which is how many domain vocabularies are best expressed and how most of them should have been.
See it for yourself
The flag, dump or tool that shows you this directly.
- Read a language's own design documents before its tutorials: Rust's RFCs, Go's design documents, the Python enhancement proposals. The rejected proposals are more informative than the accepted ones because they state the constraint that did the rejecting.
- Read the release notes for a language's first three years and find the decisions made under time pressure that are now permanent.
- Compare a language's error messages against its stated audience. The messages reveal who the designers actually pictured more reliably than the documentation does.
- For a language you are evaluating: write the smallest realistic program from your own domain in it. The friction shows up in twenty lines or not at all.
Plausible wrong readings
Stated the way a confident engineer states them.
- "One language is simply better than the others and the argument is about which." Better is only defined against an audience, a failure cost and a deployment environment. Change any of the three and the ranking changes.
- "Powerful features are always better." Every feature is also a thing a reader must know to read the code, and a thing the compiler must handle in combination with every other feature. Go's early omissions were expensive and they were not accidents.
- "You can add the guarantees later." You can add checks later. You cannot add a guarantee to a language whose existing programs violate it, which is why nullability, integer overflow and mutability are so hard to retrofit —
[[nullability]]. - "Syntax is where language design happens." Syntax is the cheapest part to change and the part users argue about most. The semantics decide what the compiler can prove and what the program can mean.
Misconceptions
The claim, and what is actually true.
Go deeper
The same idea at increasing depth. Stop wherever it stops being useful.
overview
Before deciding anything about a language, decide who writes in it and what it costs them to be wrong. A language for people who cannot tolerate a pause cannot have a garbage collector; a language for people who cannot tolerate a build step cannot have a heavy type checker. Almost every argument about language features is actually an argument about audience.
practical
When someone proposes a feature, ask what audience need it serves, what it costs every reader of the language, and which existing decision it interacts badly with. Most proposals die honestly on the second question. When evaluating a language for a project, do the same in reverse: find the audience it was designed for, and if it is not yours, work out which of its guarantees you are about to pay for without needing.
advanced
The deep constraint is that guarantees compose multiplicatively with features. Every new feature must be checked against every existing guarantee, so a language with strong guarantees gets more expensive to extend over time — which is why mature strongly guaranteed languages are slow to add things and are right to be. The corresponding failure mode of weakly guaranteed languages is the opposite: features can be added cheaply because nothing must be preserved, so they accumulate until no reader knows all of them and every combination is a corner case. Both trajectories are visible in real languages, and the audience answer is what decides which one you are choosing to live with.
How much this depends on
Nothing in this domain is true of every compiler. These say how much.
enum, and JavaScript has changed enormously since 1995 — the durable part of each story is the original constraint, not the current feature list.If you were asked this in an interview
- You are designing a language for one specific audience. Walk me from that audience to a memory-management decision without appealing to taste.
- Name a language whose design is clearly shaped by a constraint that no longer applies. What did it cost?
- When is the right answer "do not build a language"?
Connections
- Programming Languages & Runtime Internals — What the runtime must provide once these decisions are made — allocation, scheduling, dispatchThe audience answer determines what a runtime has to do; building that runtime is the other domain's subject. Ours stops at what the compiler must emit for it.