TypeScript vs JavaScript
Choose TypeScript when checking shared contracts and refactoring safely justifies maintaining the type-checking setup. Choose JavaScript when its simpler source format fits the task, and add checking where it helps. This is a choice about development-time feedback, not two unrelated runtime platforms. TypeScript builds on JavaScript; the resulting program still depends on JavaScript semantics and its execution environment.
TypeScript can catch a wrong argument shape before a test runs, but it cannot guarantee that a network response matches a declared interface. The practical choice includes a third option: JavaScript with JSDoc and TypeScript's JavaScript checking. Types are useful guardrails. They do not inspect a package arriving at your API just because you printed a reassuring label on the box.
TL;DR
- TypeScript adds static checking and type syntax to JavaScript; most type annotations do not become runtime checks.
- Plain JavaScript can also use editor tooling, JSDoc, and optional static checking.
- Validate untrusted data at runtime regardless of the source-file extension.
- Keep checking separate from bundling or type stripping; an executable file has not necessarily passed a checker.
- Migrate incrementally around useful boundaries and make the checking policy part of CI.
Quick Example
Save this complete example as total.ts in a project with TypeScript installed. Check and compile it with npx tsc --strict --target ES2020 --module commonjs total.ts, then run node total.js. It shows both a static contract and a runtime boundary.
The assertion to Record<string, unknown> allows inspecting properties after the object check; the following conditions validate their types and values. Replacing the parser with JSON.parse(text) as Line would assert a contract without enforcing it. The arithmetic is illustrative and omits tax, currency conversion, and other pricing rules.
Core Concepts
Static checking answers questions before execution
A checker reasons about declared and inferred types: whether a property exists, whether a value can be absent, or whether an argument matches a function's expected shape. The TypeScript handbook introduces its purpose and scope. The benefit depends on the actual checking policy: broad any types and unchecked assertions remove information the checker would otherwise use.
Useful types explain a meaningful contract. A discriminated union can distinguish a successful request from a failed one, so code must handle both shapes. It is less useful to build a complicated generic type that nobody can understand merely to save a small runtime branch. Prefer a contract a teammate can read and maintain.
Runtime behavior still belongs to JavaScript
The handbook's basics guide explains erased types and strictness options. A type annotation is not a validator that examines production input. JavaScript coercion, exceptions, asynchronous execution, and numeric behavior remain relevant after type syntax is removed.
Some TypeScript constructs require transformation rather than simple annotation erasure. Build tools and runtimes support different subsets, so verify the actual execution path. Do not interpret an editor's green check as proof that the deployment target understands every syntax feature the application uses.
An assertion does not establish a fact
The everyday-types guide documents type assertions as instructions to the checker, without associated runtime checking. A value obtained from a form, URL, file, database, or third-party service needs validation appropriate to that boundary. In the example, unknown keeps the uncertainty visible until the parser checks it.
Runtime validation also needs semantic rules. A number may be finite yet unsuitable as a quantity; a syntactically valid identifier may refer to a resource the caller cannot access. Static types, parsing, authorization, and business invariants solve related but separate problems.
Head-to-Head
These are tendencies to evaluate, not a ban on large JavaScript projects or a requirement that small projects avoid TypeScript. Existing libraries, team conventions, and the expected lifetime of a component can matter more than its initial line count.
Three Practical Adoption Paths
Keep JavaScript and improve its contracts
For a short utility or an established codebase, tests, clear module boundaries, and runtime validation may address the immediate risk. JSDoc can document parameters and return values while preserving .js files. TypeScript's JavaScript checking documentation explains supported checking behavior and differences from .ts files.
Use this path deliberately rather than treating the absence of a compiler as the absence of engineering obligations. Run the appropriate checks in CI, make assumptions explicit, and verify behavior at external boundaries. A small script that can delete production data needs more care than its length suggests.
Add checking without renaming everything
Start with a useful module or package and adopt an explicit configuration for JavaScript checking. Measure the kinds of mistakes found and the effort needed to maintain annotations. Keep the scope understandable. A gradual improvement that developers actually run can provide better feedback than an ambitious migration that is perpetually ignored.
Avoid mixing conflicting module settings or generated files into the checked source set. Keep the build, checker, editor, and tests aligned about which files belong to the application. Otherwise a passing check may simply have omitted the code you expected it to cover.
Adopt TypeScript at stable boundaries
Convert a bounded subsystem, define its public contracts, and work outward. Preserve behavior while migrating. Add type tests or ordinary checks for important interfaces and integration tests for real dependencies. Declare how newly added code should be checked so untyped escape routes do not quietly expand.
Enable strict checking when feasible, then address findings with real narrowing or better models. Do not silence every failure with assertions just to finish a conversion. Document unavoidable exceptions so reviewers can distinguish a deliberate boundary from an unfinished assumption.
Separate Checking From Execution
Modern runtimes may execute supported TypeScript syntax through type stripping. Node's TypeScript documentation explicitly distinguishes that capability from type checking and describes configuration limitations. Likewise, a bundler can transform a file while a separate checking step fails. Make both responsibilities visible in the release workflow.
A reasonable sequence is to run the checker, execute behavior tests, and produce the application build. Keep the results separate so an omitted checker cannot masquerade as a successful one. Verify the exact runtime, module format, and dependency assumptions used in deployment.
When adopting third-party definitions, remember that they describe a contract someone supplied. A version mismatch or inaccurate declaration can still produce a runtime failure. Verify significant integration behavior against the real dependency. TypeScript improves the feedback available to the team; it does not turn every declaration into experimentally established truth.
Best Practices
Use uncertainty explicitly at input boundaries
Treat unvalidated external values as unknown, parse them once, and return an application-level type only after checking the required structure and semantics. Share the parser where multiple entry points need the same contract.
Prefer small, legible types
Model valid states and public interfaces. Let inference handle ordinary local values. If a type requires a tutorial every time somebody edits a function, compare its maintenance cost with a simpler interface or runtime check.
Make the checker an actual release gate
Document and run the same checking command locally and in CI. A successful transpilation is a different result. Keep necessary suppressions narrow and explain the unresolved limitation beside them.
Common Mistakes
Treating an interface as input validation
Bad: cast an API response to a type and immediately trust its fields.
Correct: validate the response's structure and required values before using it. Keep authorization and business invariants as separate checks.
Measuring adoption by renamed files
Bad: convert files to .ts, fill the gaps with any, and announce that the contracts are safe.
Correct: track which important boundaries are actually checked and which runtime behaviors are tested. File extensions alone are not evidence of correctness.
Assuming TypeScript makes execution faster
Bad: attribute a runtime performance improvement to type annotations without measuring the emitted program.
Correct: profile the workload and explain any implementation change responsible for the result. Static checking primarily changes development feedback.
FAQ
Should I learn JavaScript before TypeScript?
Learn JavaScript's values, functions, objects, modules, and asynchronous behavior alongside any TypeScript study. TypeScript uses those mechanisms rather than replacing them. You do not need to master every advanced JavaScript feature before learning useful types.
Can JavaScript have static type checking?
Yes. Tools can infer types and use JSDoc to check JavaScript files. Evaluate that option when incremental adoption is more useful than immediately converting source files.
Can Node run TypeScript directly?
Supported Node versions can run supported erasable syntax, subject to documented limitations. That execution path is not a type check. Verify the deployed runtime and keep a separate checking command when you depend on static feedback.
Does TypeScript remove the need for tests?
No. It cannot establish every business rule, validate arbitrary external input automatically, or verify a deployed integration. Use types and tests for the different failures each can detect.
Related Topics
- TypeScript — type models, narrowing, and compiler configuration.
- JavaScript — the underlying language and runtime behavior.
- Python vs JavaScript — choose an execution environment and ecosystem.
- React vs Vue — apply language tooling within a component stack.
- Unit vs Integration vs End-to-End Tests — combine static checking with behavioral evidence.