Your First TypeScript Program
With TypeScript installed, the fastest way to understand it is to write a small program, compile it to JavaScript, and watch the type checker reject a deliberate mistake. This page walks through creating a .ts file, compiling it with tsc, running the output, and then iterating with watch mode and direct execution tools. By the end you will have seen the full loop — write, check, emit, run — that every TypeScript workflow is built on.
Write a hello-world file
Create a file called hello.ts. Add a typed function and call it. Notice the : string annotations describing the parameter and return type.
// hello.ts
function greet(name: string): string {
return `Hello, ${name}!`;
}
const message = greet("DevCraftly");
console.log(message);
TypeScript infers that message is a string from greet’s return type, so you do not need to annotate it. Inference handles most cases; you add annotations mainly at the boundaries — function parameters and public APIs.
Compile with tsc
Run the compiler on your file. It type-checks the code and, if there are no errors, emits a JavaScript file next to it.
npx tsc hello.ts
This produces hello.js. By default tsc targets a modern JavaScript version and writes the output in the same directory:
// hello.js
function greet(name) {
return `Hello, ${name}!`;
}
const message = greet("DevCraftly");
console.log(message);
The type annotations are gone — exactly the “type erasure” behavior that lets the result run in any JavaScript engine.
Run the output
Execute the compiled JavaScript with Node.js.
node hello.js
# Hello, DevCraftly!
That is the complete classic loop: write .ts, compile to .js, run with node. Next we will see what happens when the code is wrong.
Watch the type checker catch a bug
Change the call to pass a number instead of a string and recompile. The compiler refuses to proceed.
const message = greet(42);
// ❌ Error: Argument of type 'number' is not assignable to
// parameter of type 'string'.
npx tsc hello.ts
# hello.ts:6:23 - error TS2345: Argument of type 'number' is not
# assignable to parameter of type 'string'.
This is the entire value proposition in miniature: a bug that JavaScript would have happily run (producing "Hello, 42!" or worse in a real program) is stopped at compile time with a precise, located message.
Iterate faster with watch mode
Recompiling by hand gets tedious. Watch mode recompiles automatically whenever you save, giving instant feedback.
npx tsc hello.ts --watch
# [hh:mm:ss] Starting compilation in watch mode...
# [hh:mm:ss] Found 0 errors. Watching for file changes.
Leave it running in a terminal while you edit. Each save reruns the type check and re-emits the output.
Skip the build step with tsx
In development you often want to run TypeScript directly without a separate compile step. Tools like tsx (or ts-node) transpile on the fly.
npx tsx hello.ts
# Hello, DevCraftly!
| Command | What it does | When to use |
|---|---|---|
tsc hello.ts | Type-check + emit hello.js | Producing build output |
tsc --watch | Recompile on every save | Active development |
tsx hello.ts | Run .ts directly, no .js file | Scripts, quick runs |
node hello.js | Run the compiled output | Production / CI |
tsxandts-nodetranspile but do not fully type-check while running (they are optimized for speed). Keeptsc --noEmitin your CI pipeline so real type errors still fail the build.
Best Practices
- Annotate function parameters and return types; let inference handle local variables.
- Use watch mode (
tsc --watch) during development for instant feedback. - Reach for
tsxto run scripts quickly without an intermediate build. - Always run
tsc --noEmitin CI, since fast runners skip full type checking. - Read compiler error messages carefully — they include the file, line, and the exact mismatch.
- Keep source (
.ts) and output (.js) separated viaoutDironce a project grows beyond one file.