Home Application-development TypeScript High Performance

TypeScript High Performance

By Ajinkya Kher
books-svg-icon Book
Subscription
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
Subscription
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
About this book

In a world where a tiny decrease in frames per second impacts customer engagement greatly, writing highly scalable code is more of a necessity than a luxury. Using TypeScript you get type checking during development. This gives you the power to write optimized code quickly. This book is also a solid tool to those who’re curious to understand the impact of performance in production, and it is of the greatest aid to the proactive developers who like to be cognizant of and avoid the classic pitfalls while coding.

The book will starts with explaining the efficient implementation of basic data Structures, data types, and flow control. You will then learn efficient use of advanced language constructs and asynchronous programming. Further, you'll learn different configurations available with TSLint to improve code quality and performance. Next, we'll introduce you to the concepts of profiling and then we deep dive into profiling JS with several tools such as firebug, chrome, fiddler. Finally, you'll learn techniques to build and deploy real world large scale TypeScript applications.

Publication date:
August 2017
Publisher
Packt
Pages
230
ISBN
9781785288647

 

Chapter 1. Efficient Implementation of Basic Data Structures and Algorithms

One of the most important things to achieve optimal performance with any language is to understand the correct usage of the data types and the constructs it offers. If leveraged optimally, these features can help in producing a robust and high performance application, but if leveraged in a non-optimal fashion, the same features can adversely impact the overall performance and usability of the end product.

Let's take a look at the performance impact with the help of the following basic constructs, operations, and data structures:

  • Strings: We will take a look at string concatenation and string replacement. We will explore different ways of performing the same and understand the relative merits of the different ways in terms of their performance impact.
  • Classes and interfaces: We will explore a famous design pattern with the help of classes and interfaces and understand how a good design can help in achieving a durable and scalable application.
  • Loops and conditions: We will look at the relative performances of the different looping and conditional constructs the language offers.
  • Arrays and sorting: We will cover arrays by exploring a famous comparison-based sorting algorithm and understand how the basics of optimizations can greatly enhance the performance of an application.
  • Operators: We will take a look at the different operators that the language offers and their basic usage.
 

Strings


Let's start by taking a look at strings. In TypeScript, you can create a string in one of the following ways:

    var test1: string = 'test string';

    var test2: String = new String('test string');

For all practical purposes, you would almost always use the first way to work with strings. The only difference between the two is that string is a literal type and a preferred way to declare strings in TypeScript. String is an object type, which is essentially a wrapper object around the string. From a performance standpoint, literal types tend to perform better than object types. We can confirm this by running a simple code snippet as mentioned in the following code snippet:

    function createString_1(): string {
     return `Lorem Ipsum is simply dummy text of the printing and  
     typesetting industry. Lorem Ipsum has been the industry''s    
     standard dummy text ever since the 1500s, when an unknown    
     printer took a galley of type and scrambled it to make a type   
     specimen book. It has survived not only five centuries, but also      
     the leap into electronic typesetting, remaining essentially  
     unchanged.It was popularised in the 1960s with the release of   
     Letraset sheets containing Lorem Ipsum passages, and more  
     recently with desktop publishing software like Aldus PageMaker   
     including versions of Lorem Ipsum.`;
    }

    function createString_2(): String {
     return new String(`Lorem Ipsum is simply dummy text of the   
     printing and typesetting industry. Lorem Ipsum has been the 
     industry''s standard dummy text ever since the 1500s, when an   
     unknown printer took a galley of type and scrambled it to make a  
     type specimen book. It has survived not only five centuries, but 
     also the leap into electronic typesetting, remaining essentially  
     unchanged. It was popularised in the 1960s with the release of  
     Letraset sheets containing Lorem Ipsum passages, and more 
     recently with desktop publishing software like Aldus PageMaker 
     including versions of Lorem Ipsum.`);
    }

    // calculate time taken to create 50, 000 'strings'
    let time1: number = Date.now();
    for (let i = 0; i < 50000; i++) {
      createString_1();
    }

    let time2: number = Date.now();
    console.log('Time difference (createString_1): ', time2 - time1);

    // calculate time taken to create 50, 000 'Strings'
    time1 = Date.now();
    for (let i = 0; i < 50000; i++) {
      createString_2();
    }

    time2 = Date.now();
    console.log('Time difference (createString_2): ', time2 - time1);

The preceding code snippet creates a long string (the famous filler text mostly used as a placeholder on visual web elements) using the preceding described two ways.

Note

The multiline string is represented in TypeScript by enclosing the string within two back-tick/back-quote (`) characters. It's that easy!

Now, let's take a look at the results of executing the preceding code snippet:

Chrome (v56)

IE (v11)

Edge (v38)

createString_1() : string

3 ms

17 ms

13 ms

createString_2(): String

5 ms

42 ms

31 ms

Note

Every set of time-based results mentioned in this book is an average of five runs. For example, the average runtime of createString_1() on Chrome (v56) across five runs is 3 milliseconds. We cover the runtime across three browsers - Google Chrome version 56, Internet Explorer Version 11, and Microsoft Edge Version 38.

As you can see from the results, the literal type string does behave slightly better. The creation of the string, however, is not the most impacting operation that would affect your application. Let's take a look at some classic string manipulation operations.

String concatenation

Let's start with string concatenation. Take a look at the following code snippet:

    function createString(): string {
     return `Lorem Ipsum is simply dummy text of the printing and 
     typesetting industry. Lorem Ipsum has been the industry''s 
     standard dummy text ever since the 1500s, when an unknown   
     printer took a galley of type and scrambled it to make a type     
     specimen book. It has survived not only five centuries, but also 
     the leap into electronic typesetting, remaining essentially  
     unchanged. It was popularised in the 1960s with the release of 
     Letraset sheets containing Lorem Ipsum passages, and more 
     recently with desktop publishing software like Aldus PageMaker   
     including versions of Lorem Ipsum.`;
    }

    let time1: number = Date.now();
    let s1: string = '';

    for (let i = 0; i < 40000; i++) {
     s1 = s1.concat(createString());
    }

    let time2: number = Date.now();
    console.log('Time difference: ', time2 - time1);

    let s2: string = '';
    time1 = Date.now();

    for (let i = 0; i < 400000; i++) {
     s2 += createString();
    }

    time2 = Date.now();
    console.log('Time difference: ', time2 - time1);

The preceding snippet contains a simple function createString, which returns a long string, precisely 617 characters long. We append this string to itself 40,000 times. In the first loop, s1 does so by calling s1.concact(createString()) as many times, and in the second loop, s2 does so by calling s2+= createString() as many times. This is just a shorthand for s2 = s2 + createString().

When you analyze these loops, how long do you think they took to complete execution? Did one substantially outperform the other? Or, do you think they're just two different ways to append a string to itself, and has no impact on performance? Well, here are the results:

Chrome (v56)

IE (v11)

Edge (v38)

Loop 1

7 ms

50 ms

36 ms

Loop 2

60 ms

183 ms

163 ms

The preceding results are an average of five runs. As you can clearly see, Loop 1 outperforms Loop 2 massively.

Just to understand how massive the impact on the end result would be, consider the following scenario. Consider a social web application, which when the user starts on his/her end loads the entire friend/contact list and the entire conversation history with each contact. Hypothetically, assume that the user has 100 contacts, and with each contact he/she has 10 conversations. Let's further assume each conversation to be essentially the preceding string in our example appended to itself 40,000 times. Using our preceding results (considering Chrome as the web browser), with an inefficient implementation of string concatenation the page load time would be 100 * 10 * 60 milliseconds = 60,000 milliseconds = 60 seconds or 1 minute. Now, that's a really long time the user has to wait before the application becomes functional. Such sluggishness can greatly impact the application's usability and user engagement.

As opposed to this, if the user was to load the same application with the efficient string concatenation technique, the user wait time would be 100 * 10 * 7 = 7,000 milliseconds = 7 seconds, which is not quite that bad.

If you're starting to appreciate the powers of efficient implementation, we're just getting started. The time performance isn't even the critical impact factor. The resource that is greatly impacted is memory!

When you do s1.concact(createString()), you're essentially appending the string on the same object, as a result of this the total memory consumption is increased only by the memory needed by the additionally appended string.

However, when you do s2+= createString(), you're essentially creating a new object each time. If s2 was pointing to a memory block which occupies x bytes, now after one call to s2+= createString(), it is pointing to a new memory block which occupies x + x = 2x bytes. Thus, at each step, the total memory consumption is greatly increased.

After 40,000 such invocations, the first loop will result in total memory consumption of 40,000 * x bytes, whereas the second loop will result in total memory consumption of x + 2x + 3x + ... + 40,000 * x bytes = 800, 020 * x bytes. As you can see, way greater memory is consumed by the second loop.

Running some tests, it is observed that on Chrome, the shallow size occupied by the concatenated string during the execution of the first loop is 802, 160 bytes, or ~0.8 MB. The shallow size occupied by the concatenated string during the execution of the second loop is 8, 002, 160 bytes, or ~8 MB.

The implications of this are dastardly, and the user can experience application crashes and freezes due to memory overload, which would lead to poor user experience and if not immediately corrected, very soon zero usability and lost business.

Now, let's take a look at string replacement.

String replacement

Take a look at the following code snippet:

    function createString(): string {
      return `Lorem Ipsum is simply dummy text of the printing and     
      typesetting industry. Lorem Ipsum has been the industry''s  
      standard dummy text ever since the 1500s, when an unknown   
      printer took a galley of type and scrambled it to make a type  
      specimen book. It has survived not only five centuries, but 
      also the leap into electronic typesetting, remaining   
      essentially unchanged. It was popularised in the 1960s with the   
      release of Letraset sheets containing Lorem Ipsum passages, and   
      more recently with desktop publishing software like Aldus  
      PageMaker including versions of Lorem Ipsum.`;
    }
    let baseString: string = createString();
    const replacementCharacter: string = '|';
    let time1: number = Date.now();
    for (let i = 0; i < 50000; i++) {
        baseString.split(' ').join(replacementCharacter);
      }
    let time2: number = Date.now();
    console.log('Time difference (SplitJoin): ', time2 - time1);
    time1 = Date.now();
    for (let i = 0; i < 50000; i++) {
         baseString.replace(/ /g , replacementCharacter);
        }
    time2 = Date.now();
    console.log('Time difference (Replace_w_RegExp): ', time2 - 
    time1);

In the preceding code snippet, we start with the same base string as in previous examples. Here, we have the objective of replacing every whitespace with the pipe character ('|'). For example, if the base string were "High Performance TypeScript", after applying our replace logic, the resultant string would be "High|Performance|TypeScript".

Now, this string has a total of 127 whitespaces. We loop over the base string and perform this replacement a total of 50,000 times. Let's understand the following two different replacement options we are leveraging:

  • SplitJoin: Let's take a look at the following replacement option code snippet:
        baseString.split(' ').join(replacementCharacter);

Let's split this into two pieces to better understand how this works, such as split and join:

  • split: This method splits the string into an array of strings by separating the string into substrings, as specified by an optional splitter character. Here, we are using the whitespace (' ') as the splitter character. So, after we apply baseString.split(' '), baseString is split into a string array of length 128.
  • join: This method joins or combines all elements of a string array into a string, as specified by an optional combiner character. Here, we specify the pipe ('|') as the combiner character. Thus, the result of baseString.split(' ').join('|') is a resultant string of the same length as the base string (617 characters), with all the whitespaces replaced by the pipe character.
  • Replace with RegEx: Let's take a look at the following replacement option code snippet:
        baseString.replace(/ /g, replacementCharacter);

The replace method returns a new string with some or all matches of a pattern replaced by a replacement character. We again choose the pipe character as the replacement character, and the pattern we choose is a regular expression '/ /g'. We specify a RegEx by enclosing it within the forward slash character ('/'). Here, we specify a white space as the RegEx.

Note

We include the g flag to indicate a global search and replace. If we are to not include it, then only the first occurrence of a whitespace would be replaced. There is also an "i" flag that can be used, which would mean ignore case. In this case, it does not apply as we are not bothered about the casing with the whitespace character.

If you were to analyze the two methods, you would note that in the SplitJoin method you perform two passes around the input string -- first to break it down into smaller chunks, and then to combine those chunks back into a whole string. This would involve as many string concatenations as the number of chunks it got split into in the first place. The Replace with RegEx options seems to take one pass around the input string, creating a new string copy, character by character, looking for a pattern match along the way and replacing it with the replacement character. An educated guess would be that the Replace with RegEx option has a better performance.

Let's take a look at the results to find out! Note that these are a result of 127 replacements on a 617 character long string, done 50,000 times:

Chrome (v56)

IE (v11)

Edge (v38)

SplitJoin

195 ms

405 ms

452 ms

Replace with RegEx

139 ms

144 ms

139 ms

 

As we predicted, the Replace with the RegEx method outperforms the SplitJoin method. The difference between the two methods is more significant on IE and Edge. In terms of real-world usage, string replacement is even more widely used than concatenation. Any web/mobile application with a frontend framework that works with multiple backends, or even a single backend, can run into the string replacement scenarios where data interpretation across different systems varies and there's a need to replace some strings to convert it into a format that the current system can understand. If done inefficiently, the results could once again lead to a slower/sluggish UI rendering, higher network latencies, incapability in using the application's full feature set due to the slower performance making the application unusable and gives you a poor user experience overall.

As you can see, efficient implementation of basic data type manipulations have a far reaching impact on the performance and usability of an application. Let's look at some other data structures and constructs.

 

Classes and interfaces


For folks acquainted with object-oriented concepts, the use of classes and interfaces is well known. It is the most basic way to encapsulate information. Classes encapsulate a common data schema, and operations for all of its instances, and interfaces just encapsulate a schema which needs to be implemented by any class that implements the interface.

These are the basics which you must already be familiar with. Instead of introducing some simple examples of class and interface declaration, let's deep dive into an advanced usage scenario, very close to how real-world modern web/mobile applications are structured. Pay close attention to the following problem statement.

Let's assume we are a vendor company for another firm, lets say XYZ. Now, XYZ contacts us and asks us to implement Social Network Feed Generator(SNFG). XYZ is building an application to combine social network feed from all possible social networks, and displays this aggregated feed to it's users. Let's assume a scenario in which a single universal key or token that can authenticate a user against all of his/her social networks (let's not worry about the security at this moment, as we are not trying to understand cryptography here!). XYZ plans to pass us this key for it's users, and in return, wants an aggregated feed based on several different criteria such as most recent updates, most popular updates, and so on. Now, our job is simple --to provide an API to XYZ that it can use in it's application.

Let's get started building SNFG in an object-oriented way, making it extensible for future updates. Take a look at the following code snippet:

     // publicly accessible
     export enum FeedStrategy { Recent, Popular, MediaOnly };

     // internal
     enum SocialMediaPlatform { Facebook, Instagaram, Snapchat, 
     Twitter, StackOverflow };

     // publicly accessible
     export interface IFeed {
        platformId: SocialMediaPlatform;
        content: string;
        media: string
     }

     // publicly accessible
     export interface IFeedGenerator {
         getFeed(limit?: number): IFeed[];
     }

     // internal
     class RecentFeedGenerator implements IFeedGenerator {
       private authenticate(universalKey: string): void {
         // autheticate user identity
     }

     public getFeed(limit?: number): IFeed[] {
      /* if authenticated
       {
          custom algorithm to optimally calculate most recent social 
          media feed for the user, across all the available social 
          media platforms
       } */
        return []; /* return the results, returning empty array here to     
        satisfy method return type */
      }
     }

     // internal
     class PopularFeedGenerator implements IFeedGenerator {
       private authenticate(universalKey: string): void {
        // autheticate user identity
      }

     public getFeed(limit?: number): IFeed[] {
     /* if authenticated
        {
           custom algorithm to optimally calculate most popular  
           social media feed for the user, across all the available   
           social media platforms
        } */
       return []; /* return the results, returning empty array here to  
       satisfy method return type */
     }
    }

     /* you can have as many classes as you desire, or you can also  
     combine  multiple feed fetching algorithms into single class */

     // publicly accessible
     export function FeedGeneratorFactory(feedStrategy: FeedStrategy,  
     universalKey: string): IFeedGenerator {
       let feedGenerator: IFeedGenerator = null;
       switch (feedStrategy) {
          case FeedStrategy.Recent: {
             feedGenerator = new RecentFeedGenerator();
          break;
        }
          case FeedStrategy.Popular: {
             feedGenerator = new RecentFeedGenerator();
          break;
        }
      }
      return feedGenerator;
     }

We design a module called Feeder (we will be looking at modules and namespaces in closer details in the next chapter). Within this module, we build the crux of our SNFG. The idea is that XYZ can use the exported members (publicly accessible) of this module to achieve its objective. Let's take a look at the exported members of Feeder in the following code snippet:

    export enum FeedStrategy { Recent, Popular, MediaOnly };

    export interface IFeed { ... };

    export interface IFeedGenerator { ... };

    export function FeedGeneratorFactory(feedStrategy: FeedStrategy,  
    universalKey: string): IFeedGenerator { ... }

The most important exported function is FeedGeneratorFactory. XYZ can call this function with it's desired FeedStrategy (which is also exposed as an enum), and universalKey for the user it wishes to access the feed of. With this information, what FeedGeneratorFactory does is completely abstracted. This function returns an interface type IFeedGenerator. This is all that the developers of XYZ should really care about. We expose an interface IFeedGenerator, any class implementing which implements the function getFeed which takes in an optional limit parameter to specify the upper limit on the number of desired feed items. From XYZ's perspective, they can simply invoke the FeedGeneratorFactory function, and call the getFeed() function on the returned IFeedGenerator implementation. Internally, based on the FeedStrategy function, we return a different class. We could as well have put all of the logic into a single class (although not a good idea). How we implement this is completely up to us and is hidden from XYZ.

Note

This is a classic example of abstraction and encapsulation, one of the key concepts of object-oriented design.

The feed method returns an array of IFeed type, which is also an exposed interface. Each IFeed consists of platformId to identify the social media platform the feed belongs to, a string content, and string media, which is a URI pointing to the media used in the feed, if any. The contents of IFeed can be discussed with XYZ and modified if needed.

All other members, such as the actual classes implementing the core logic of feed fetch, sort, organize, most likely cache, and several other concepts which would need to be introduced as the scale of this increases, are internal to us and totally abstracted from XYZ.

Now from XYZ's perspective, knowing this information, all they need to do is import our module and use it in their application. One possible usage could be as illustrated in the following code snippet:

    import * as Feeder from "./vendor";

    class FeedRenderer {
      private recentFeedGenerator: Feeder.IFeedGenerator;
      private popularFeedGenerator: Feeder.IFeedGenerator;

      constructor(universalKey: string) {
        this.recentFeedGenerator = Feeder.FeedGeneratorFactory 
        (Feeder.FeedStrategy.Recent, universalKey);
        this.popularFeedGenerator = Feeder.FeedGeneratorFactory
        (Feeder.FeedStrategy.Popular, universalKey);
       }

     public getRecentFeed(): Feeder.IFeed[] {
      return this.recentFeedGenerator && this.recentFeedGenerator
      .getFeed();
    }

     public getPopularFeed(): Feeder.IFeed[] {
      return this.popularFeedGenerator &&   
      this.popularFeedGenerator.getFeed(10);
   }
}

There are several ways to import and export modules, which we will explore in the next chapter. In the preceding example, we are explicitly including a reference to the vendor.ts file which we looked at earlier.

With this setup, XYZ gets access to the exposed members of our Feeder module. They simply call the FeedGeneratorFactory function to get access to two IFeedGenerator types, such as one that fetches the most recent feeds, and one that fetches the most popular feeds. From their standpoint, they don't really care whether it's the same implementation or two different implementations under the wraps. All they care is, it is of the type IFeedGenerator. The FeedRenderer class would be called into by XYZ's UI elements to render the fetched feed.

Note

The preceding example is a classic illustration of a design pattern called Factory Method Pattern. In our case, the FeedGeneratorFactory function is the factory method that returns an object without it's user (XYZ) explicitly having to specify the exact class of the object that will be created.

The following diagram summarizes the factory pattern we've been discussing thus far:

Apart from the flexibility this offers to you as a vendor, in terms of internally managing the concrete implementations, it also makes it easier for you to manage version updates and rollbacks. Any new releases can be rolled out without breaking the consumer contract (explicit consumer-driven contracts can be enforced too), and if anything were to break, you can rollback to an earlier version, and the experience from the consumer's perspective would be seamless.

This helps the consumer as well, in that if the preceding exposes API, becomes or is an industry standard, and there are multiple such vendors available to choose from in the market, the consumer can seamlessly switch between multiple vendors without having to make major changes to their core code base.

As you can see, when leveraged wisely, classes and interfaces can have a high impact on the longevity and scalability of your application, and consequently on it's performance.

 

Loops and conditions


Loops are one of the most common constructs in any language. It is a mechanism to execute the same piece of logic multiple times. There are multiple ways to do so in TypeScript. Let's take a look at some in the following code snippet:

    const arr: Array<number> = [];
    let temp: number = 0;

    const randomizeArray = () => {
      for (let i = 0; i < 50000; i++) {
          arr[i] = Math.floor(Math.random() * 100000);
        }
     }

    // dummy method
    const dummy = () => null;

    // for...in
    const forinLoop = () => {
      let dummy: number;
      for (let i in arr) {
          dummy = arr[i];
        }
     }

    // for...of
    const forofLoop = () => {
       let dummy: number;
       for (let i of arr) {
       dummy = i;
      }
    }

    // naive
    const naiveLoop = () => {
       let dummy: number;
       for (let i = 0; i < arr.length; i++) {
          dummy = arr[i];
       }
    }

    const calculateTimeDifference = (func: () => void): number => {
       randomizeArray();
       const time1: number = Date.now();
       func();
       const time2: number = Date.now();
       return time2 - time1;
    }

    console.log('Time Difference (forof): ', calculateTimeDifference
    (forofLoop));
    console.log('Time Difference (forin): ', calculateTimeDifference
    (forinLoop));
    console.log('Time Difference (naive): ', calculateTimeDifference
    (naiveLoop));

In the preceding code snippet, we declare an array arr. We initialize it with 50,000 random numbers. Then, we measure the time performance of three loops such as forof loop, forin loop, and naive loop. Before we dig into the performance results, let's take a look at a side note on the syntax.

Note

We have declared the array using the generic array type, const arr: Array<number> = [];. We can also declare it using the data-type-array syntax, const arr: number [] = [];. Also, we've declared the functions as constants, using the Lambda expressions. We could also write them as old fashioned functions, function forinLoop() { ... }. We are also passing functions as parameters, as in, calculateTimeDifference(forofLoop). The function declaration of this function is interesting, const calculateTimeDifference = (func: () => void): number => { ... }. This basically says that calculateTimeDifference is a function that takes a function which takes zero arguments and returns void as an argument, and returns a number.

Let's take a look at the following performance of these loops now:

Chrome (v56)

IE (v11)

Edge (v38)

forofLoop

1.4 ms

7 ms

4.4 ms

forinLoop

22.8 ms

16.2 ms

15.2 ms

naiveLoop

1.6 ms

11 ms

7.4 ms

As can be seen, the forof loop performs the best, followed by naive loop, followed by the forin loop. These results are actually a little misleading. For one, the performance of the naive loop can be greatly improved by storing the arr.length property in a variable and using that in the loop statement, instead of accessing the array's property each time. In addition to that, the preceding example is not really a strong case in point for the forin loop. Let's take a look at another example which would highlight the advantages of using the forin loop:

    const calculateTimeDifference = (func: () => void): number => {
     const time1: number = Date.now();
     func();
     const time2: number = Date.now();
     return time2 - time1;
    }

    const generateGuid = (): string => {
     return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,  
     (c: string) => {
     const r = Math.random() * 16 | 0,
     v = c == 'x' ? r : (r & 0x3 | 0x8);
     return v.toString(16);
     });
    }

    interface Map {
       [key: string]: string;
     }

    const map: Map = {};

    for (let i = 0; i < 50000; i++) {
         map[(i+1).toString()] = generateGuid();
       }

    // for...in
    const mapForIn = () => {
     let dummy: string;
     for (let key in map) {
         dummy = key;
       }
   }

    // for...of
    const mapForOf = () => {
        let dummy: string;
        const keys = Object.keys(map);
        for (let key of keys) {
          dummy = key;
       }
    }

    // naive
    const mapForNaive = () => {
      let dummy: string;
      const keys = Object.keys(map);
      for (let i = 0; i < 50000; i++) {
         dummy = keys[i];
     }
    }

    console.log('Time Difference (map:forof): ',  
    calculateTimeDifference(mapForOf));
    console.log('Time Difference (map:forin): ', 
    calculateTimeDifference(mapForIn));
    console.log('Time Difference (map:naive): ', 
    calculateTimeDifference(mapForNaive));

The preceding code snippet generates the following result:

Chrome (v56)

IE (v11)

Edge (v38)

forofLoop

8.6 ms

12.2 ms

10.8 ms

forinLoop

9.2 ms

8.2 ms

6.6 ms

naiveLoop

11.8 ms

10.4 ms

9.4 ms

 

On Chrome, the forof loop outperformed the forin loop, but only slightly. On IE and Edge, the forin loop recorded the best performance, while the naive loop remained the least efficient across all browsers. Thus clearly, the forin loop is the recommended for looping over objects such as dictionaries. Based on the use case, one should choose the option that most makes sense.

Conditional statements are one of the most commonly used constructs in any language. In TypeScript, you may implement a conditional using the if..else statement or the switch…case statement. Let's take a look at these constructs.

Consider the following if..else block:

    let decision: number = Math.floor(Math.random() * 100) + 1; //  
    random // number between 1 and 100
    if(decision === 1) {
        // do something
     } else if(decision === 2) {
        // do something
     } else if (decision === 3) {
     // do something
     } else if (decision === 4) {
     // do something
     } else if (decision === 5) {
    // do something
     } else if (decision === 6) {
     // do something
     } else if (decision === 7) {
     // do something
     } // ...
       // ...
       // ...
     } else (decision === 100) {
     // do something
     }

The performance of the preceding code block would be the worst if decision is 100, making 100 comparisons before our condition evaluates to true. If you notice, this if..else chain is equivalent to a linear search, the time complexity of which in terms of Big-Oh notation is O(n) where n is the total number of comparisons. In the worst case, you would make n comparisons like in the preceding example. In the best case, the very first comparison would evaluate to true. On average you would make n/2 comparisons. If you have data points with the probability of different values that decision can take, you can optimize the preceding if..else chain by making the higher probability comparisons before the others. The time complexity of this optimized approach would still be O(n).

In order to improve the performance in a generic case, especially when the probabilities are unknown, you can refactor the if..else chain to reflect binary search. In binary search, you eliminate half of the choices at each stage, thereby yielding a logarithmic time complexity O(lgn). Look at the following code snippet to get a better idea:

    let decision: number = Math.floor(Math.random() * 100) + 1; //     
    random // number between 1 and 100
    if (decision < 50) {
         if (decision < 25) {
             if (decision < 12) {
                 if (decision < 6) {
                     // all results < 6
                       if (decision < 3) {
     // compare decision with 1 and 2
     } else {
     // compare decision with 3, 4 and 5
            }
           } else {
           // all results >=6 and < 12
           if (decision < 10) {
                if (decision < 8) {
                     // compare decision with 6 and 7
                } else {
                         // compare decision with 8 and 9
                       }
                     } else {
                              // compare decision with 10 and 11
                            }
                       }
                      } else { /* ... */ }
                      } else { /* ... */ }
                      } else { /* ... */ }

One thing that would strike you immediately with the preceding code snippet is that even though it performs better than the linear checks, it's readability is a big pain. Code maintenance is a big factor that would be impacted especially when working on large team projects. It's a good time to look at the switch…case statement:

    let decision: number = Math.floor(Math.random() * 100) + 1; //  
    random // number between 1 and 100
    switch (decision) {
     case 1: {
        // do something
     break;
       }
     case 2: {
       // do something
     break;
       }
       // ...
      // ...
     case 100: {
     // do something
     break;
     }
     default: {
      // do something
     }
    }

On first look, the switch…case statement seems to be similar to the linear if..else block. However, it is internally based on the browser you're running the script on, the rendering engine would optimize the number of comparisons. Given this fact combined with a much better code readability, you should choose the switch…case statement whenever you can make single value comparisons.

Note

For range comparisons, you would still need to use the if..else statement, which can actually render optimal performance if implemented efficiently.

 

Arrays and sorting


In TypeScript, you can declare arrays using the classic square bracket syntax, for example, let arr: number[] = [74, 46, 32]; or, you can also declare arrays using the Array<elementType> syntax, for example, let arr: Array<number> = [74, 46, 32];.

Let's take a look at a sorting example with arrays:

    const arr: number[] = [];
    let temp: number = 0;

    const randomizeArray = () => {
       for (let i = 0; i < 50000; i++) {
           arr[i] = Math.floor(Math.random() * 100000);
          }
       }

    // naive
    const naiveSort = () => {
     for (let i = 0; i < arr.length; i++) {
         for (let j = 0; j < arr.length - 1; j++) {
             if (arr[j] > arr[j + 1]) {
                   temp = arr[j];
                   arr[j] = arr[j + 1];
                   arr[j + 1] = temp;
                 }
              }
          }
      }

    // optimized
    const optimizedSort = () => {
     let swapped: boolean = true;
     while (swapped) {
           for (let j = 0; j < arr.length - 1; j++) {
                   swapped = false;
               if (arr[j] > arr[j + 1]) {
                        swapped = true;
                        temp = arr[j];
                        arr[j] = arr[j + 1];
                        arr[j + 1] = temp;
                      }
                   }
               }
          }

    const calculateTimeDifference = (func: () => void): number => {
        randomizeArray();
        const time1: number = Date.now();
        func();
        const time2: number = Date.now();
        return time2 - time1;
      }

In the preceding code snippet, we declare an array and initialize it with 50,000 elements, each of which is a random number between 0 and 100,000. The sorting algorithm we're looking at is bubble sort. Bubble sort works by bubbling the largest element to the end of the array in each pass (assuming a non-decreasing sort).

The number of passes needed by this algorithm is one less than the number of elements. Let's take a look at the following diagram to get a better idea:

As you can see in the preceding diagram, we start with a 6 element array. In Pass 1, we make four comparisons starting from the first element, and performing a swap each time we detect an order mismatch. For example, 87 > 12, which is a mismatch and hence we do a swap. 87 > 45, so we swap again. 87 < 93, and so we do not swap. Finally 93 > 16 and 99 > 93, so we swap. At the end of Pass 1, we ensure that the largest element is at the end of the array.

We perform four more such passes, ensuring that the largest element during that pass ends up toward the end of the array.

Generalizing this for an n-element array, we can see that the total comparisons we will end up making is (n-1)*(n-1), which when talking in terms of Big-Oh gives us a time complexity of O(n2).

The preceding concept is implemented in the preceding code snippet under the function naiveSort. While reading this, you may have already detected an inefficiency in the preceding approach. We need not have (n-1) passes by rule of thumb. As you can see at the end of Pass 4, we already have a sorted array and do not really need Pass 5. So, when do we know when to stop? Answer: When there was no swap performed during a pass we stop. This optimized approach is implemented in the preceding code snippet under the function optimizedSort.

Note

The preceding mentioned that optimization is one possible way to optimize the naive bubble sort. Another option is to perform one comparison less in each subsequent pass, the reason being that each pass ensures that the largest element is at the end, so we keep making redundant comparisons toward the end of the array at each pass. This approach combined with the preceding approach will result in an even more optimal sorting algorithm. The optimized approach has better runtime, but in terms of Big-Oh complexity, they are still O(n2) algorithms.

The results, not surprisingly, are as follows:

Chrome (v56)

IE (v11)

Edge (v38)

naiveSort

7563 ms

NaN

NaN

optimizedSort

1 ms

58 ms

45 ms

As you can see, a simple optimization has massive impacts on performance. On Chrome, it outperforms the naive implementation by gigantic amounts. The optimized sort runs as fast as a millisecond! And the naive sort takes almost 8 seconds! On IE and Edge, the findings are even more dire. As expected, the optimized sort runs fairly quickly. However, the naive sort never completes and the web page crashes even before the sort can complete!

Once again, the time performance despite having contrasting comparison is not the most critical factor. CPU utilization and to some extent memory consumption are the resources most severely impacted. Just to give you some context, the average CPU utilization for a web page running advanced chat applications is only 0.5-3%. However, when the preceding script is executed on a web page, the CPU utilization shoots up to 30-35%! This is one of the reasons why the web page crashes. Note that the preceding percentage values are on a two core processor with a maximum speed of 2.81 GHz.

There are several web/mobile applications that may perform the sorting of such a massive scale. Consider a travel booking application for instance. Among the several thousand options, different levels of sorting are performed, sorting based on prices, timings, and so on. An optimal algorithm powering these sorts is of vital importance. Any inefficiency will manifest itself as either UI spinners spinning forever, or application/web page crashes. Either of these scenarios would take a massive hit on your application's user engagement and ultimately on your business.

 

Operators


Let's now take a look at the several available operators you can leverage in TypeScript. Quite a few have already been implicitly introduced to you via code snippets from previous sections. Let's formally declare all the operators:

  • Arithmetic operators: The most obvious class of operators are the arithmetic operators, which are used to perform arithmetic operations. The most obvious arithmetic operators are addition (+), subtraction (-), multiplication (*), division(/), increment (++), decrement (--), and modulus (%). The modulus operator returns the result of what remains after a number is divided by another as a whole. For example, take a look at the following code snippet:
        const x: number = 9/4; //evaluates to 2.25

        const y: number = 9%4; // evaluates to 1

 

As the usage of the remaining operators is natural, let's look at the next class of operators.

  • Relational operators: These operators are used to perform comparisons in TypeScript. The common relational operators are equals (==), does not equal (!=), greater than (>), less than (<), greater than or equals (>=), and less than or equals (<=).

Note

The equals (==) operator exists in TypeScript in two flavors, strict mode and lenient mode. The == is the lenient mode, while the === or triple equals is the strict mode. With strict mode, the evaluation of the expression to true is less than lenient mode, because in lenient mode there is implicit type conversion whereas in strict mode there is no such conversion. For example, 16 == '16' evaluates to true, whereas 16 === '16' evaluates to false.

  • Logical operators: These operators are used to perform manipulations on conditions. Most common logical operators are AND (&&), OR (||), and Negation (!). These operators can be used in various contexts, for example:
        const condition: boolean = x > 0 && x < 10; // simple   
        condition AND

        this.instanceVariable && this.instanceVariable.DoOperation(); 
        /* AND used to check for non-null instance variable, and then    
        perform the operation on that instance. */

        // This could also be written as

        if (this.instanceVariable) { 
             this.instanceVariable.DoOperation(); 
         }

 

  • Bitwise operators: Apart from the preceding mentioned operators, there are several bitwise operators, which perform advanced bit-level manipulations. Some common bitwise operators are as follows:
  • Left shift (<<): Left shift shifts the bits in a number to the left, by the specified number of bits, for example, (x << 2)
  • Right shift (>>): Right shift shifts the bits in a number to the right, by the specified number of bits, for example, (x >> 2)
  • AND (&): The AND operator performs a bit-level AND on the bits of the two specified operands, for example, (x & 4, which is x & 0...0100)
  • OR (|): The OR operator performs a bit-level OR on the bits of the two specified operands, for example, (x | 4, which is x | 0...0100)
  • XOR (^): XOR performs a bit-level XOR on the bits of the two specified operands, for example, ( x ^ y)
  • Negation (~): Negation flips the bits of a number, for example, (~x)

Note

Some Important Bitwise Operator Hacks 1. x << 1 is the same as x * 2. 2. x >> 1 is the same as x / 2. 3. x & 0 will always equal 0. 4. x | 1 will always equal 1. 5. x ^ x will always equal 0. 6. ~0 is 11...11 (as many bits as the system supports).

For the sake of completeness, lets also explore the typeof operator, instanceof operator, and Compound AssignmentOperators with the help of the following code snippet:

        // 1. compound assignments
        let num: number = 40;
        num *= 2; // num1 equals 80. this is same as num = num * 2

        // 2. typeof
        const str: String = 'test';
        console.log(typeof num); // outputs number
        console.log(typeof str === 'string'); // outputs true

        // 3. instanceof
        const arr: number[] = [1,2,3];
        class ABC {};
        const abc: ABC = new ABC();

        console.log(arr instanceof Object); // outputs true
        console.log(arr instanceof Array); // outputs true
        console.log(abc instanceof Object); // outputs true
        console.log(abc instanceof ABC); // outputs true
        console.log(arr instanceof ABC); // outputs false
  • Compound assignment operator: The first operator in the preceding code snippet is a compound multiplication assignment operator. This is the following two-step process:
    1. First is multiplication, temp = num * 2.
    2. Second is assignment, num = temp.

Thus, writing num *= 2 is equivalent to writing num = num *2. Similar to compound multiplication assignment, we can have compound addition, subtraction, and division assignments. Take a look at the following operators:

  • typeof: The second operator in the preceding code snippet is the typeof assignment. As can be seen, typeof num prints number and typeof str prints string.
  • instanceof: The third operator in the preceding code snippet is the instanceof operator. As can be seen in the preceding code snippet, this can be used to query a given object to find which class it is an instance of. This can be useful in determining the implementing class of an object passed to a method that accepts an interface type as a parameter.
 

Summary


In this chapter, we looked at some basic data structures, constructs, and algorithms, and explored the performance impact of leveraging these in an efficient manner. We understood how these performance tweaks play a major role toward the durability, scalability, maintainability, and the performance of your application.

After exploring these basic constructs, let's take a look at some declaration basics that TypeScript offers in our next chapter.

About the Author
  • Ajinkya Kher

    Ajinkya Kher is a full stack developer, currently working at Microsoft on the communications infrastructure for Skype and Microsoft teams. He is passionate about modern scalable architectural patterns, efficient problem solving, and process design. His experience and expertise is in the .NET middle tier/backend and modern HTML5 frontend frameworks.

    He loves getting his hands dirty with the latest and the greatest technologies out there. In his free time, you can find him winning Hackathons, building mobile applications, and lifting weights. He has been playing tennis for more than a decade and has been an ardent fan of cricket and Sachin Tendulkar since childhood, his weekends are thus often spent playing these two sports. He also likes to practice and read about spirituality and philosophy whenever he can.

    Check out his latest podcast Building Modern Web Applications using React/Redux/Angular2/RxJs on YouTube and you can also follow him on LinkedIn.

    Browse publications by this author
Latest Reviews (3 reviews total)
Rychlé doručení, vynikající kvalita
I've not gotten to reading this book yet only skim through it. Over all it appears to have the information that I need.
Formatting is lower quality than the other books that I purchased.
TypeScript High Performance
Unlock this book and the full library FREE for 7 days
Start now