Before I start the discussion of over-engineering in TypeScript, I want to give a basic overview of how JavaScript and more strongly-typed languages currently deal with types. All types in JavaScript (with the exception of the object
type) are called primitive values
. Primitive values that are used a lot in JavaScript, include values with the type of string
, number
, and boolean
. All primitive values are immutable. For example, for strings, this means that once you have done the assignment let testString = ‘test’
, you cannot mutate it by doing something like testString[0] = ‘r’
to get rest
. You have to re-assign it, testString = ‘rest’
.
JavaScript is a loosely typed language. A variable in JavaScript can be reassigned to a value of any type such as number
, string
, or object
. For example, you can do testString = 4
. Notice how the string is now a number and the name of the variable is still testString
.
In contrast, C# can be an example of a more strongly-typed language. To explain this, let me give you an example. This is how you assign a string to a variable in C# string testString = “test”;
. Instead of using the var
or let
keyword, we declare the type of the variable, (string
), right when we assign it. Then, if we try to reassign it to a value of a different type, for example, by doing it like this testString = 4;
, you get the error error CS0029: Cannot implicitly convert type ‘int’ to ‘string’
:
testString
will always be a string. I want to note here that you should try to avoid adding types to the names of your variables, because, as you have seen in the loosely-typed language case, it gives the wrong type (testString
became a number), and in more strongly-typed languages, sometimes the type is already shown very clearly.
TypeScript is a language that allows JavaScript applications to have features that more strongly-typed languages often have. You can also choose how strict you want type checking to be and how much of your existing JavaScript code you want to migrate to TypeScript. You can make type checking very strict and do a full migration over to TypeScript, or you can even add TypeScript to your JavaScript projects to prepare for the future and not use it at all. In this article, I will go through the advantages and disadvantages of using TypeScript. This will give us a better idea of when to avoid over-engineering in TypeScript.
Before I start explaining the advantages and disadvantages of using TypeScript, I feel that I should start with an explanation of the advantages of not using it. For the purpose of keeping the length of this article not too long, I am only going to go through the primary way client-side scripting is done on the web in general — which is by using JavaScript. Discussion of related languages such as CoffeeScript or Elm, will not be included in this article. I will also explain the use of more JavaScript than TypeScript in server-side code.
One advantage of using more JavaScript is the speed that it brings. JavaScript does not have to be compiled and you can see results immediately. It does not have to be configured — you don’t have to create a configuration file (tsconfig.json
) and then add and alter its configuration. No extra configuration and no compile time means that you can prototype applications easily to see how your designs, data-fetching, and other features will turn out.
Another advantage of using more JavaScript is its maturity. It has been, at least in the last few years, the primary way to do scripting on the client-side/frontend. Sometimes, it is even used in the backend (Node.js). Because it has been so mature, it already includes ways of handling types depending on the library used. In Mongoose, for example, you can define schemas using code that looks like this: const userSchema = new Schema({ firstName: String })
. firstName
here is defined as having a String
type. Also because of its maturity, JavaScript already includes ways of handling types in documentation — JSDoc being the first one that comes to mind. If you already use JSDoc, adding TypeScript might seem excessive.
TypeScript is often useful, even for the smallest applications or applications in a Microservices architecture. However, if you have smaller applications it means that there is a higher chance that you can easily infer what the type is. Also, you lose out when you do additional configuration for TypeScript and compile every time to do a few type checks. If your codebase is small, type checking can be more easily done in unit tests, using libraries such as Chai, without the need for TypeScript. Which brings me to another point — you can already check for types without using TypeScript by using test libraries, but TypeScript offers feedback sooner. Before you run tests, and it can sometimes offer feedback automatically whilst you are coding (Visual Studio Code). If you have been given a new application to take care of, have a look at what the existing tests do.
I’m not going to go through all of the advantages or disadvantages of using TypeScript here because there are so many. Please bear that in mind when I start to discuss using TypeScript in this section.
One of the first advantages of using TypeScript that I want to describe is its tooling. If you already use tools like Visual Studio Code (Version 1.43.2) or Visual Studio 2019, there is a lot of support for the language right now specifically in these tools. I will refer to Visual Studio Code as VS Code in the rest of this article.
One feature that is not often available in other tools is, hover information in VS Code. This allows you to quickly see information about types and other information for methods, parameters, variables, or other features of JavaScript, as you hover your mouse over code. You can also hold the alt key when you hover your mouse over the function name of a function call, to see the function that you are calling:
This is helpful when the function you are calling is outside of your view or many lines away.
Signature help (a tooltip shown when you type in (
after a function name to call that function) is another helpful feature that you sometimes don’t find in other good tools that are available right now. The example below shows this happening when you type in errorMessage(
to call the errorMessage
function:
The ability to quickly and automatically remove unused imports is another particularly useful feature that you don’t find in other tools. When you hover over grayed-out imports, a blue indicator that allows you to do this is shown:
A disadvantage of using TypeScript that I haven’t discussed yet, is the change to your project’s file structure and build tools. You would have to rename all your JavaScript files to use the .ts
file extension. If you use JSX, you need to rename extensions to .tsx
. The amount of work that you need to do to migrate would then depend on how many files your project contains, but you could always gradually migrate your files over time.
Also, dealing with declaration and source map files can be a hindrance. For example, when you install declaration files for React and react-dom, you need to add more packages to your project, npm install --save-dev @types/react @types/react-dom
. Integration with frameworks is also a hassle and you would also need to adjust your build tools (Babel and webpack are some examples of these) to support TypeScript. However, depending on which frameworks and build tools you use, good official resources and documentation probably already exist to support this.
One argument against using TypeScript for server-side code is that you could just switch to other type-safe languages on the backend. However, that would depend on what skill set other contributors have. For instance, if your other team members all already know how to write in another type-safe language, then this option is a good use of everyone’s skills. Try to pick the best language based on the task that is required, if you want to deal with data, concurrency, or even types, TypeScript might not be the best choice for each of those topics. The advantage of using TypeScript over other languages is that, well… it is basically just JavaScript… but with types!
Another issue that might cause problems when migrating away from JavaScript is what the official TypeScript documentation refers to as ‘sequentially added properties’. This is when you create an empty object: let person = {}
and then assign new keys and values to it person.name = ‘John’
. This is an often used and documented pattern in JavaScript. However, TypeScript, by default, does not allow this. The fastest way of getting around this would be to just give person
the any
type, but this comes at the cost of a weaker type check. You can also move properties into the object literal or use a TypeScript interface
.
The last feature of TypeScript that I will discuss is the ability to control the strictness of checks on your code. The strictNullChecks
option is great at preventing bugs because the null
reserved keyword and the undefined
value are common sources of errors in JavaScript. An example I can give is let a:number = null
. In this case, the value of a
can be null
, undefined
or NaN
(you can get NaN
when you set a
to 0/0
). With the strictNullChecks
option set to false
, you might wonder which of the three values a
is set to if all three are allowed. One extra thing to note about this option is that I found that strictNullChecks
seems to already be set to true by default when you don’t use the strictNullChecks
option in the configuration file.
Another option I tested, noImplicitAny
did not work in the way that was expected. We would expect it to raise errors when there is an implied any
type, but this was not the case. You can try this in Visual Studio Code and see that let b: any
is shown when you hover your mouse over b
, let b; console.log(b);
:
However, in this example:
function example (c) { return c; } console.log(example(5));
The noImplicitAny
option works and throws the error: Parameter ‘c’ implicitly has an ‘any’ type. TS7006
:
It is still an excellent option and works as intended, but it does not cover all the cases we would expect it to.
The cost or overhead of adding types to your code is far outweighed by its positives in the long term or for large projects. Remember to always use TypeScript correctly. Learn more about its features and about your own project first, before you consider using it. This will help you to decide if you should migrate to TypeScript, how many of its features you want in your projects, and if your project already has similar features.
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
6 Replies to "Discussing the over-engineering trap in TypeScript"
I’d just like to add that at least IntelliJ and Atom both provide features like intellisense / unused import removal etc on es6 modules. You don’t need TS for just those features.
The disadvantage is configuring typescript? I can do that in less than an hour, including webpacj and babel. The self-documenting nature of types makes your code so much readable and avoids stupid errors when you don’t know the structures you’re using, even if you wrote them 10 minutes ago. The sheer speed of typed development in excellent editors like VS code is already an extremely useful advantage, and of you take into account that your whole program can become type safe, it’s a no brainier to go for Typescript. JavaScript really becomes a scripting language, or a begginers language. Any serious project I’ve ever seen ends up migrating to Typescript unless it has robust testing set up, seasoned developers with a lot of knowledge of the project, and few changes or features over time. On top of all of this, it exposes you to typing (although structural, not nominal) which lowers the learning curve for statically typed languages. Embrace Typescript as soon as you can, or be forced into it later.
“If your codebase is small, type checking can be more easily done in unit tests, using libraries such as Chai, without the need for TypeScript.”
I know this isn’t what you are promoting here, but I have heard this before as an argument against moving to Typescript. However I don’t think unit tests can replace Typescript. It is not a good idea to expect the programmer who wrote the bugs to know exactly which unit tests to write in order to catch them. It is also much more difficult to write unit tests that would catch every type safety bug than to just use a language that makes it near impossible. And then write unit tests as well. You can focus your unit tests on behaviour rather than something the language and static analysis tooling could do for you instead.
There are several entire classes of errors commonly seen in JavaScript code that will never be seen in Typescript code if configured well. For example, trying to call a method on an undefined variable. Yes, migrating to Typescript is a lot of work, but like the article says, you can do it a bit at a time and you still get some benefits along the way. 🙂
Disclaimer: I work with that “even backend” thing called node.js and I find it fascinating compared to strongly typed languages like Java. MUCH less code does the exact same thing. Easier to read and write.
Have you ever used a combination of eslint, JSDoc and webstorm? I had zero type errors thanks to that.
Oh, and I sometimes write “string” or “number” in the name of the variable because I know I won’t be reassinging it (because I mostly use const). When type is important and comes from outside of a function, I do type checks.
I follow KISS principle. Keep it simple, stupid (no offence meant).
Small functions, small files, one function aims to do one thing at a time. One module does what it’s supposed to do, and nothing more. Types are defined via JSDoc.
So far every time I thought about using TS I caught myself wondering what benefits it would bring. Found none for me personally so far. But it does make it harder to work – compile before execution, change file names, code becomes more verbose. It has its benefits but so far I’ve managed just fine with just js. And linter. And JSDoc. And unit tests. And open api docs. And type checks. And TS won’t remove the need of any of those.
This could be different in front end. This could be different for your project and your contributors. This is just my experience.
If the next project i work with uses TS, I’ll use it without an issue. If not, I won’t push for TS ASAP. I’ll aim to use the best tool for the job.
The job being implementing business logic in an optimal way that scales well and doesn’t take a very long time to realize. Oh, and make sure you have unit, implementation and end to end testing. Don’t skip on tests, regardless of TS.
Thanks! I use Atom quite a lot, so I’ll check that out.
The article doesn’t have enough experience I suppose. I started with Javascript and after moving to typescript there’s no turning back. Commercial projects are just too big to be maintained with Javascript. Typescript makes the development faster because your team members don’t have to know the code before hand and can safely write code that works as expected. In the high-tech industry everybody works with typescript. In react everybody works with typescript. That is the future and you better embrace it sooner than later