Understanding of Basic Data Structures in TypeScript

Understanding of Basic Data Structures in TypeScript

Data structures are a complex topic every programmer must master on their journey to becoming a world-class software developer.

Basic Data Structures in TypeScript. Queues, stacks, linked lists, and generics

Data structures are a complex topic every programmer must master on their journey to becoming a world-class software developer.

Unfortunately, the average web programmer misses out on the opportunity to learn much about them. In this tutorial, we will explore how to write the three basic data structures one first encounters in a computer science curriculum: linked lists, stacks, and queues.

TypeScript allows us to create data structures in the forms typically seen in traditional, object-oriented programming languages, such as C++, Java, and C#.

These forms are well-known, well-documented, and can be found in any data structures textbook or website.

Prerequisites

You will need a code editor, preferably Visual Studio Code. You will also need to have Node.js and Git installed, and be familiar with the basics of TypeScript

Setup

The starter code is available on GitHub. The completed code is also available on GitHub. Open a terminal window and run the following commands to get the starter code up and running:

git clone [email protected]:richarddprasad/ts-data-struct-starter.git

cd ts-data-struct-starter

npm install

npm start
(after a few seconds) Ctrl+C
npm start

npm runs the start:dev and build:dev scripts in parallel (these can be found in the package.json file).

The first time we run npm start, the code will not have been fully built yet and will crash. This is why we must kill and restart it. Your terminal output should resemble the following:

We can ignore the errors coming from src/queue/queue_examples.ts. They will go away once we implement our first data structure (the queue).

The Basics of a Data Structure

Data structures are all about organizing collections of data in a particular way.

This organization is defined by how we add and remove data. We could dream up hundreds of possible data structures, but there are certain ones that are an essential part of software development: linked lists, stacks, queues, priority queues, hash tables, and binary trees.

Linked lists, stacks, and queues are the most elementary of these data structures.

Basic operations

Data structures differ from each other by the manner in which they organize data. We define the rules for this system of organization by providing a set of special methods:

  1. An insert method for inserting an item into the data structure.
  2. A remove method for removing an item.
  3. An isEmpty method to test whether any items are present.

These are generic names. Typically, a particular data structure will have a special name for a given operation. Some data structures will have additional methods that are unique to them.

Queues

Queues are the first data structure we will implement. They are known as first-in, first-out (FIFO) data structures and can be visualized as follows:

Queue terminology

The beginning of a queue is called the front and the end of it is called the rear.

The first element (i.e., data item) inserted into the queue is the first to be removed, i.e., the element at the front of the queue.

The last element that was inserted is the last to be removed, i.e., the one at the rear of the queue. Any subsequent element inserted into the queue is placed at the rear.

Queue operations

Queues implement the basic operations with the following names:

  1. enqueue: Insert an element at the rear of the queue; this element becomes the new rear.
  2. dequeue: Remove the element at the front of the queue; the subsequent element becomes the new front.
  3. isEmpty: Check if the queue contains any elements.

Here’s a quick visualization of how a queue operates:

The dequeue operation affects the front of the queue, while the enqueue operation affects the rear.

An example of a queue in the real-world is a grocery checkout line: The first person in line is the first to checkout and the last person is the last to checkout. Checking out from a grocery store is made into an orderly and efficient process thanks to queues.

Operating systems use queues to keep track of each character you type on your keyboard. A network server can store client requests in a queue, responding to each one in the order they came in.

One additional operation common to queues is called peek, though it can also be known by other names:

4. peek: Retrieve the element at the front of the queue without removing it.

Our queue is an array-based implementation, which means it has a finite size. It is also possible to create a list-based implementation, which we will do after we implement a linked list. Since our queue has a size limit, we can test whether or not it is full:

5. isFull: Check to see if our queue contains the maximum amount of items it can hold.

We can also add in a size method to retrieve how many elements are currently in the queue. We will not implement it for our queue, but feel free to do so as an exercise. It’s simply a matter of returning the value of the length property.

Queue implementation

Open the project inside of Visual Studio Code, then open the file src/queue/queue.ts. It will contain the following:

export class Queue {
    // private queue: [];
    private length: number; // number of elements currently in the queue
    private readonly maxSize: number; // maximum number of elements queue can contain

    public constructor(maxSize: number) {
        // Make sure maxSize is at least 1
        this.maxSize = maxSize > 0 ? maxSize : 10;
        this.length = 0;
    }

    public isEmpty() {

    }

    public isFull() {
        
    }
    
    public enqueue() {

    }

    public dequeue() {

    }

    public peek() {

    }
    
    public queueContents(): void {
        
    }
}

The queue property is currently commented out. We will fix that soon, but first, we need to take a little detour and explore a TypeScript feature called generics.

A Quick Look at Generics

Our queue is storing a collection of elements. We will store these elements in an array (queue). We will specify the maximum number of elements the array can hold (maxSize), and we will keep track of how many elements are currently in the queue (length).

JavaScript arrays can store elements of mixed types, but this is only because JavaScript arrays are not ordinary arrays: they are a special object designed to mimic the functionality of an array.

(JavaScript does provide a means to create arrays that are more conventional in nature. For those who are curious, click see MDN docs.

In languages with a true array structure, arrays can only hold elements of a single type. We will duplicate this here by specifying a type for queue.

But what type do we give it? number? string? We can specify a set of possible types using a union type: number | string | boolean. Unfortunately, there are thousands, if not millions, of possible types, so using a union type is not practical.

Fortunately, TypeScript gives us the ability to use a variable type which we can specify later. This is accomplished using generics and can be implemented as follows:

export class Queue<T> {
    private queue: T[];
    private length: number; // number of elements currently in the queue
    private readonly maxSize: number; // maximum number of elements queue can contain

    public constructor(maxSize: number) {
        // Make sure maxSize is at least 1
        this.maxSize = maxSize > 0 ? maxSize : 10;
        this.length = 0;
        this.queue = new Array<T>(this.maxSize);
    }
    ...
}

We specify that our class accepts a generic parameter: class Queue. Using T as the parameter name is a common convention. Our queue array is an array of type T.

Within the constructor, we are initializing our queue array to be a new JavaScript Array of type T: this.queue = new Array(this.maxSize);.

Back to Our Queue

Let’s implement each of the basic operations one-by-one. We will implement them from easiest to hardest:

  1. isEmpty: Return true if length is equal to zero, or false otherwise.
   public isEmpty(): boolean {
        return this.length === 0;
    }

2. isFull: Return true if length is equal to maxSize, or false otherwise.

  public isFull(): boolean {
        return this.length === this.maxSize;
    }

3. peek: Return the front element without removing it; throw an exception if the queue is empty.

 public peek(): T {
        if (this.isEmpty()) {
            throw new Error('Queue is empty');
        }
        return this.queue[0];
    }

4. enqueue: Insert element at the rear if space is available. If we attempt to insert into a full queue, we will cause a queue overflow error.

  public enqueue(newItem: T): void {
        if (this.isFull()) {
            throw new Error('Queue overflow');
        } else {
            this.queue[this.length++] = newItem; // post-increment adds 1 to length after insertion
        }
    }

5. dequeue: Remove and return the front element. This one is more challenging because we need to shift all the elements over. If we try to remove from an empty queue, we will cause a queue underflow error.

  public dequeue(): T {
        if (this.isEmpty()) {
            throw new Error('Queue underflow');
        }
        
        const retval = this.queue[0];

        for (let i = 0; i < this.length; i++) {
            this.queue[i] = this.queue[i + 1];
        }

        this.length--; // we need to decrease length by 1
        return retval;
    }

Removing an element from a queue is a time-consuming operation because we have to move everything over.

However, this only applies to array-based queues such as this one. Later, we will implement a list-based queue that does not suffer from this performance issue.

6. queueContents: This is not officially part of the queue implementation, but can be useful for debugging purposes. It will print out the contents of the queue.

  public queueContents(): void {
        console.log('Queue Contents');
        for (let i = 0; i < this.length; ++i) {
            console.log(`queue[${i}]: ${this.queue[i]}`);
        }
    }

Here is the complete implementation of our queue:

export class Queue<T> {
    private queue: T[];
    private length: number; // number of elements currently in the queue
    private readonly maxSize: number; // maximum number of elements queue can contain

    public constructor(maxSize: number) {
        // Make sure maxSize is at least 1
        this.maxSize = maxSize > 0 ? maxSize : 10;
        this.length = 0;
        this.queue = new Array<T>(this.maxSize);
    }

    public isEmpty(): boolean {
        return this.length === 0;
    }

    public isFull(): boolean {
        return this.length === this.maxSize;
    }
    
    public enqueue(newItem: T): void {
        if (this.isFull()) {
            throw new Error('Queue overflow');
        } else {
            this.queue[this.length++] = newItem; // post-increment adds 1 to length after insertion
        }
    }

    public dequeue(): T {
        if (this.isEmpty()) {
            throw new Error('Queue underflow');
        }
        
        const retval = this.queue[0];

        for (let i = 0; i < this.length; i++) {
            this.queue[i] = this.queue[i + 1];
        }

        this.length--; // we need to decrease length by 1
        return retval;
    }

    public peek(): T {
        if (this.isEmpty()) {
            throw new Error('Queue is empty');
        }
        return this.queue[0];
    }
    
    public queueContents(): void {
        console.log('Queue Contents');
        for (let i = 0; i < this.length; ++i) {
            console.log(`queue[${i}]: ${this.queue[i]}`);
        }
    }
}

Creating queues

Let’s take a look at how we can create queues to hold different types of elements. Here is an example of creating a queue of numbers:

const numberQueue = new Queue(100);

This creates a queue capable of storing 100 numbers.

We can also create a queue of strings:

const stringQueue = new Queue(50);

This creates a queue that can hold 50 strings.

We are not limited to built-in JavaScript types. Let’s see how we can create a queue of customers:

interface Customer {
    name: string;
    age: number;
    isMember: boolean; // many large grocery store chains have membership programs
    rewardsCard?: string;
}

// A checkout lane with 10 customers
const checkoutLine = new Queue<Customer>(10);

Let’s make a queue of numbers with a maximum size of 100. Then, let’s fill the queue up until it can no longer hold any more numbers, and then tear it down:

// Create a number queue capable of storing 100 numbers
const nq = new Queue<number>(100);

// Fill the queue up with random numbers
while(!nq.isFull()) {
    nq.enqueue(Math.floor(Math.random() * 1000));
}
nq.queueContents();

// Empty out the queue
while(!nq.isEmpty()) {
    console.log(`${nq.dequeue()}`);
}
nq.queueContents();

These examples are provided in the project file src/queue/queue_examples.ts. You can uncomment the line import ‘./queue/quest_examples’; in index.ts to run the code.

Running Tests

Before we move on to the next section, let me mention that I have provided tests for each data structure.

If you are familiar with unit testing, feel free to run the test suites and add your own tests as well. Open a second terminal window or tab and run the following command to run the test suites:

npx jest

Stacks

Stacks are similar to queues, except they insert and remove elements from the same location instead of from opposite ends of the data structure. Stacks are a last-in, first-out (LIFO) data structure.

Stacks play important roles in the inner workings of operating systems and JavaScript engines. For example, stacks are used to keep track of function calls.

Recursive functions that do not resolve to a base case can grow the stack until it hits its size limit, causing a stack smashing error, i.e., a “stack overflow.”

Stack terminology and operations

The end from which we insert and remove elements is called the top of the stack. Stacks implement the basic operations with the following names:

  1. push for insertion.
  2. pop for removal.
  3. top for retrieving the top element without removing it.

Stacks also have an isEmpty method, as well as an isFull method if it is an array-based stack. We can visualize a stack as follows:

Stack implementation

Open the file src/stack/stack.ts. You will find the following skeleton code:

export class Stack<T> {
    private stack: T[];
    private length: number;
    private readonly maxSize: number;

    public constructor(maxSize: number) {
        this.length = 0;
        this.maxSize = maxSize;
        this.stack = new Array<T>(this.maxSize);
    }

    public isEmpty(): boolean {
        return this.length === 0;
    }

    public isFull(): boolean {
        return this.length === this.maxSize;
    }

    public push(newItem: T): void {
        throw new Error('Implementation not provided');
    }

    public pop(): T {
        throw new Error('Implementation not provided');
    }

    public top(): T {
        throw new Error('Implementation not provided');
    }

    public stackContents(): void {
        console.log('Stack Contents');
        for (let i = 0; i < this.length; ++i) {
            console.log(`stack[${i}]: ${this.stack[i]}`);
        }
    }
}

We only need to implement the push, pop, and top methods. The implementations for the other methods have already been provided.

Stacks are easier to implement than queues because we do not have to shift elements over when removing existing ones. However, we do have to pay close attention to the array index position represented by the length property.

  1. push: Adds an element to the top of the stack.
   public push(newItem: T): void {
        if (this.isFull()) {
            throw new Error('Stack overflow');
        }

        this.stack[this.length++] = newItem;
    }

If we attempt to push into a stack that is already full, we get a stack overflow error. Note how we use the post-increment operator on the length property.

2. pop: Removes and returns the element at the top of the stack.

  public pop(): T {
        if (this.isEmpty()) {
            throw new Error('Stack underflow');
        }

        const retval = this.stack[--this.length];
        return retval;
    }

If we pop from an empty stack, we get a stack underflow error. Note how we use the pre-decrement operator on the length property.

3. top: Retrieves the element at the top of the stack without removing it.

   public top(): T {
        if (this.isEmpty()) {
            throw new Error('Stack is empty');
        }

        return this.stack[this.length - 1];
    }

Notice how the top of the stack is at length minus one.

A quick stack example

Open the file src/test/stack.test.ts. Near the bottom of the file, you will find code for reversing a string using a stack:

describe('reverse a string using a stack', () => {
    const text = 'Hello, World!';

    // Create a stack large enough to hold our text
    const stack = new Stack<string>(text.length);

    test('text is reversed', () => {
        // Add each character from the text to the stack
        text.split('').forEach(c => stack.push(c));

        let reversed: string[] = [];

        // Remove each character from the stack until the stack is empty
        while (!stack.isEmpty()) {
            reversed = reversed.concat(stack.pop());
        }

        // Run our test
        expect(reversed.join('')).toBe('!dlroW ,olleH');
    });
});
Linked Lists

A traditional, non-JavaScript array has a fixed size and cannot grow or shrink. The only way to alter the size of an array is to create a new array of the desired size and copy the contents from the old array to the new one.

The reason for this requires an understanding of how memory allocation works, which is beyond the scope of this article.

Linked lists are an alternative to arrays, and their main benefit over arrays is that they can grow and shrink far more easily.

The caveat is that linked lists are much slower than arrays when accessing data. The position of any given element in an array can be calculated directly:

The elements in a linked list do not exist as a contiguous block in memory, but are distributed throughout it.

We will see how this is done shortly, but the main thing to keep in mind is that we cannot calculate an element’s position in a linked list. We must traverse the list until we find what we are looking for, and in the worst case, what we are looking for might be at the end of the list.

A data wrapper

Before we can implement a linked list, we must create a class that wraps our data.

This class is typically called Node (not to be confused with Node.js), and, in addition to holding onto our data, will also contain a reference to another Node .

Open the file src/linked_list/node.ts and take a look at its contents:

// DS = Data Structure
export namespace DS {
    export class Node<T> {
        public item: T | null;
        public next: Node<T> | null;

        public constructor(item: T | null = null) {
            this.item = item;
            this.next = null;
        }
    }
}

Let’s break the code down:

  1. We are placing our Node class into a namespace called DS. A Node object already exists in the global scope, so we must encase our own into a separate namespace to avoid conflicts.
  2. Our Node class accepts a generic parameter of type T.
  3. The item property holds our data. The constructor provides a default value of null if no argument is provided. This is just for convenience.
  4. The next property will link one Node to another.
  5. Both item and next are public properties. Usually, properties within a class are private or protected, with getters and setters providing access to the properties. Here, we are making an exception. A linked list can have millions of nodes, and the overhead of using getters and setters will be excessive. It is better to give direct access to these properties to enhance performance.
  6. item and next can also be null. We could also use undefined instead of null. We will see why we want to allow null (or undefined) values when we implement our linked list.

Linked list terminology and operations

A linked list can be implemented in different ways. They can be singly-linked, which is what we will implement. They can also be doubly-linked, which means in addition to a next pointer, we also have a prev (previous) pointer.

We are going to use a version that uses dead or sentinel nodes to mark the beginning and end of the list. Sentinel nodes allow us to write less code to handle inserting and removing elements from the list.

The beginning of the list is called the head and the end is called the tail. An empty linked list consists of two sentinel nodes, head and tail, with the head node pointing to the tail node:

Linked lists implement insert and remove operations, however, there are multiple options for how these operations may be implemented. We can also provide more than one implementation for each, which is what we are going to do.

Our linked list will contain the following methods:

  1. insertFirst: Inserts an element at the beginning of the list.
  2. insertLast: Inserts an element at the end of the list.
  3. removeFirst: Removes the element at the end of the list.
  4. remove: Removes an element with a given key.
  5. contains: Checks to see if an element with a given key is in the list.
  6. isEmpty: Checks to see if the list contains any nodes other than head or tail.
  7. getFirst: Retrieve the first element in the list without removing it.

Linked list implementations can vary in terms of the basic operations they provide as well as the names of those operations.

For example, remove is sometimes called delete. The reason I chose some of these specific methods will become clear later on.

Linked list implementation

Open the file src/linked_list/linked_list.ts and look over the code skeleton provided. A convenience method called listContents is provided to help with debugging.

import { DS } from './node';

export class LinkedList<T> {
    private head: DS.Node<T>;
    private tail: DS.Node<T>;

    constructor() {
        this.head = new DS.Node<T>();
        this.tail = new DS.Node<T>();
        this.head.next = this.tail;
    }

    public isEmpty(): boolean {
        throw new Error('Method not implemented');
    }

    public insertFirst(item: T): void {
        throw new Error('Method not implemented');
    }

    public insertLast(item: T): void {

    }

    public removeFirst(): T | null {
        throw new Error('Method not implemented');
    }

    public remove(searchKey: T): T | null {
        throw new Error('Method not implemented');
    }

    public contains(searchItem: T): boolean {
        throw new Error('Method not implemented');
    }

    public getFirst(): T | null {
        throw new Error('Method not implemented');
    }

    public listContents(): void {
        let cur = this.head.next;

        while (cur && cur !== this.tail) {
            console.log(`${cur.item}`);
            cur = cur.next;
        }
    }
}

Our linked list contains only two properties, the sentinel nodes head and tail. Our constructor sets up the following relationship:

Our sentinel nodes will never hold any data, hence why we have allowed the item property to be null.

The tail node does not point to another node, so we have allowed the next property to be null as well. We could have tail point back to head and create a circular list.

Let’s now implement the remaining operations.

  1. isEmpty: Returns true if head.next points to tail.
 public isEmpty(): boolean {
        return this.head.next === this.tail;
    }

2. insertFirst: Add a new node to the beginning of the list.

  public insertFirst(item: T): void {
        // Encapsulate our item into a Node object
        const newNode = new DS.Node<T>(item);

        newNode.next = this.head.next;
        this.head.next = newNode;
    }

It is helpful to draw or diagram out the state of our linked list so we can more easily see what we need to do.

3. getFirst: Retrieve the first element without removing it.

 public getFirst(): T | null {
        if (this.isEmpty()) {
            throw new Error('List is empty');
        }

        return this.head.next ? this.head.next.item : null;
    }

In a non-empty list, this.head.next would never be null. However, TypeScript does not know that, hence why we had to introduce some conditional logic in our return statement.

The last method we will implement together will be remove. Solutions for the remaining methods will be provided.

4. remove: Remove a node given a key.

  public remove(searchKey: T): T | null {
        if (this.isEmpty()) {
            throw new Error('List is empty');
        }

        // rv = retval or return value
        let rv: DS.Node<T> | null = null;

        // cur = current
        let cur: DS.Node<T> = this.head;

        // Advance our cur pointer to the node right before our matching node
        while (cur.next && cur.next.item !== searchKey) {
            cur = cur.next;
        }

        if (cur.next) {
            rv = cur.next;
            cur.next = cur.next.next;
            rv.next = null;
        }

        return rv && rv.item ? rv.item : null;
    }

We need the help of a third node, cur (current), to help us remove a specific node.

Here is the complete code for our linked list with solutions for the remaining methods:

import { DS } from './node';

export class LinkedList<T> {
    private head: DS.Node<T>;
    private tail: DS.Node<T>;

    constructor() {
        this.head = new DS.Node<T>();
        this.tail = new DS.Node<T>();
        this.head.next = this.tail;
    }

    public isEmpty(): boolean {
        return this.head.next === this.tail;
    }

    public insertFirst(item: T): void {
        // Encapsulate our item into a Node object
        const newNode = new DS.Node<T>(item);

        newNode.next = this.head.next;
        this.head.next = newNode;
    }

    public insertLast(item: T): void {
        const newNode = new DS.Node<T>(item);

        let cur: DS.Node<T> | null = this.head;

        // Advance our cur pointer to just before the tail node
        while (cur && cur.next !== this.tail) {
            cur = cur.next;
        }

        if (cur) {
            newNode.next = this.tail;
            cur.next = newNode;
        }
    }

    public removeFirst(): T | null {
        if (this.isEmpty()) {
            throw new Error('List is empty');
        }

        let rv: DS.Node<T> | null = this.head.next;

        if (rv) {
            this.head.next = rv.next;
            rv.next = null;
        }

        // We are returning the data, not the node itself
        return rv ? rv.item : null;
    }

    public remove(searchKey: T): T | null {
        if (this.isEmpty()) {
            throw new Error('List is empty');
        }

        // rv = retval or return value
        let rv: DS.Node<T> | null = null;

        // cur = current
        let cur: DS.Node<T> = this.head;

        // Advance our cur pointer to the node right before our matching node
        while (cur.next && cur.next.item !== searchKey) {
            cur = cur.next;
        }

        if (cur.next) {
            rv = cur.next;
            cur.next = cur.next.next;
            rv.next = null;
        }

        return rv && rv.item ? rv.item : null;
    }

    public contains(searchItem: T): boolean {
        if (this.isEmpty()) {
            throw new Error('List is empty');
        }

        let rv: boolean = false;
        let cur: DS.Node<T> | null = this.head;

        // Traverse the list in search of a matching item
        while (cur && cur.next !== this.tail) {
            if (cur.next && cur.next.item === searchItem) {
                rv = true;
                break;
            }
            cur = cur.next;
        }

        return rv;
    }

    public getFirst(): T | null {
        if (this.isEmpty()) {
            throw new Error('List is empty');
        }

        return this.head.next ? this.head.next.item : null;
    }

    public listContents(): void {
        let cur = this.head.next;

        while (cur && cur !== this.tail) {
            console.log(`${cur.item}`);
            cur = cur.next;
        }
    }
}

As an exercise, you can try to come up with your own solutions.

List-Based Queues and Stacks

As mentioned before, I chose to construct our linked list with a specific collection of methods.

We can create queues and stacks from a linked list, and the methods I chose will let us create them easily. Study the following code and see if you can understand how it works.

List-based queue

import { LinkedList } from '../linked_list/linked_list';

export class QueueList<T> {
    private list: LinkedList<T>;

    public constructor() {
        this.list = new LinkedList<T>();
    }

    public isEmpty(): boolean {
        return this.list.isEmpty();
    }

    public enqueue(item: T): void {
        this.list.insertLast(item);
    }

    public dequeue(): T | null {
        return this.list.removeFirst();
    }

    public peek(): T | null {
        return this.list.getFirst();
    }

    public queueContents(): void {
        this.list.listContents();
    }
}

List-based stack

import { LinkedList } from '../linked_list/linked_list';

export class StackList<T> {
    private list: LinkedList<T>;

    public constructor() {
        this.list = new LinkedList<T>();
    }

    public isEmpty(): boolean {
        return this.list.isEmpty();
    }

    public push(item: T): void {
        this.list.insertFirst(item);
    }

    public pop(): T | null {
        return this.list.removeFirst();
    }

    public top(): T | null {
        return this.list.getFirst();
    }

    public stackContents(): void {
        this.list.listContents();
    }
}

We see that we no longer need an isFull method for either one. List-based stacks and queues can grow to an indefinite size.

Our queue no longer has to shift elements over after a dequeue operation, however, an enqueue operation must traverse the entire list to get to the end in order to insert a new element.

We could also have implemented our stack and queue using the built-in JavaScript Array object.

Our linked list a partial recreation of the Array object. This does not mean JS arrays are actually implemented using a linked list; the underlying implementation can vary from engine to engine.

Conclusion

I hope you found this tutorial helpful and insightful. Thank you for reading !

Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

What's new in Bootstrap 5 and when Bootstrap 5 release date?

What’s new in HTML6

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

Top 19 Front-End Web Development Tools to Consider in 2020

Top 19 Front-End Web Development Tools to Consider in 2020

In this post, we are putting curated list of top tools with key features and download links "Top 19 Front End Web Development Tools to Consider in 2020"

Front-End development is one of the critical skill for web developers as there is a lot of demand for programmers with excellent front-end development skills.

If you've never experienced client-side web development, learning the plethora of front-end technologies can be difficult, but you can achieve your target.

The one thing which I want to tell you that a good knowledge of HTML, CSS, and JavaScript is a must for any front-end developer. Even though we are living in the era of frameworks and libraries, but knowledge of these fundamental technologies cannot be undermined.

In this post, we are putting curated list of top tools with key features and download links "Top 19 Front End Web Development Tools to Consider in 2020"

1. TypeScript:

TypeScript is an open-source front end scripting language. It is a strict syntactical superset of JavaScript which adds optional static typing. It is specially designed for development of large applications and compiles to JavaScript.

Features:

  • TypeScript supports other JS libraries
  • It is possible to use this Typescript on any environment that JavaScript runs on
  • It supports definition files that can contain type information of existing JavaScript libraries, such as C/C++ header files
  • It is portable across browsers, devices, and operating systems
  • It can run on any environment that JavaScript runs on

Download link: https://www.typescriptlang.org/index.html#download-links

2. Backbone:

Backbone.js gives structure to web applications by offering models with key-value binding and custom events.

Features:

  • Backbone.js allows developers to develop one-page applications
  • Backbone.js has a simple library used to separate business and user interface logic
  • This tool makes code simple, systematic and organized. It acts as a backbone for any project
  • It manages the data model which also includes the user data and display that data on the server side
  • It allows developers to create client side web applications or mobile applications

Download link: http://backbonejs.org/

3. jQuery:

jQuery is a widely used JavaScript library. It empowers front-end developers to concentrate on the functionality of different aspects. It makes the things easy like HTML document traversal, manipulation, and Ajax.

Features:

  • QueryUI facilitates to make highly interactive web applications
  • It is open source and free to use
  • It provides a powerful theme mechanism
  • It is very stable and maintenance friendly
  • It offers an extensive browser support
  • Helps to create great documentation

Download link: http://jquery.com/download/

4. AngularJS:

AngularJS is another must-have tool for front-end developers. It is an open-source web application framework. It helps to extend the HTML syntax for web applications. It simplifies front-end development process by developing accessible, readable and expressive environment.

Features:

  • It is an is open source, completely free, and used by thousands of developers around the world
  • It offers to create RICH Internet Application
  • It provides option to write client side application using JavaScript using MVC
  • It automatically handles JavaScript code suitable for each browser

Download link: https://angularjs.org/

5. HTML5 Boilerplate:

HTML5 Boilerplate help in building fast, robust, and adaptable web apps or sites. It is a set of files that developers can download, which provide a foundation for any website.

Features:

  • It allows developers to use HTML5 elements
  • It is designed by keeping progressive enhancement in mind
  • Normalize.css for CSS normalizations and common bug fixes
  • Apache Server Configs to improve performance and security
  • It offers optimized version of the Google Universal Analytics snippet
  • Protection against console statements causing JavaScript errors in older browsers
  • Extensive inline and accompanying documentation

Download link: https://html5boilerplate.com/

6. ONE Subscription

ONE Subscription is an effective web-development kit that allows getting access to a great number of top-quality digital products. It boasts tons of high-quality items that make it possible to launch fully-fledged blogs, online-stores, landing pages, and other websites. In general, you will be provided with thousands of themes and templates, different plugins and graphic elements, additional services, and other advantages.

Features:

  • professional support for all items;
  • regular updates;
  • security (you can easily cancel your subscription within 14 days after its starts if you have not downloaded anything from the database);
  • unlimited yearly license;
  • useful services (as an example, template customization or installation).

Download link: https://one.templatemonster.com/

7. Npm:

Npm is the Node package manager for JavaScript. It helps to discover packages of reusable code and assemble them in powerful new ways. This web development tool is a command-line utility for interacting with a said repository that aids in the package.

Features:

  • Discover and reuse over 470,000 free code packages in the Registry
  • Encourage code discovery and reuse within teams
  • Publish and control access to namespace
  • Manage public and private code using the same workflow

Download link: https://www.npmjs.com/

8. CodeKit:

Codekit is a front-end web development tool. This tool provides support to build websites faster. It combines, minifies and syntax-checks JavaScript. It also optimizes images.

Features:

  • CSS changes are injected without need of reloading the entire page
  • Combine scripts to reduce HTTP requests.
  • Minify code to reduce file sizes
  • Works automatically with most languages without trouble

Download link: https://codekitapp.com/

9. Sass:

Sass is the most reliable, mature, and robust CSS extension language. This tool helps to extend the functionality of an existing CSS of a site like variables, inheritance, and nesting with ease.

Features:

  • It is straightforward and easy to use front end tool to write any code
  • Supports language extensions such as variables, nesting, and mixins
  • Many useful functions for manipulating colors and other values
  • Advanced features like control directives for libraries
  • It offers well-formatted, customizable output

Download link: http://sass-lang.com/

10. WebStorm:

WebStorm brings smart coding assistance for JavaScript. It provides advanced coding assistance for Angular, React.js, Vue.js and Meteo. It also helps developers to code more efficiently when working with large projects

Features:

  • WebStorm helps developers to code more efficiently when working with large projects
  • It provides built-in tools for debugging, testing and tracing client-side and Node.js applications
  • It integrates with popular command line tools for web development
  • Spy-js built-in tool allows tracing JavaScript code
  • It provides a unified UI for working with many popular Version Control System
  • It is extremely customizable to perfectly suite various coding style
  • It offers built-in debugger for client-side code and Node.js apps

Download link: https://www.jetbrains.com/webstorm/download/#section=windows

11. Chrome Developer Tools:

The Chrome Developer Tools are a set of debugging tools built into Chrome. These tools allow developers to do wide varieties of testing which easily saved lots of time.

Features:

  • It allows adding custom CSS rules
  • Users can view Margin, Border, and Padding
  • It helps to Emulate Mobile Devices
  • Possible to use dev tools as editor
  • User can easily disable browser's caching when dev tool is open

Download link: https://developer.chrome.com/devtools

12. Grunt:

Grunt is a popular task runner on NodeJS. It is flexible and widely adopted. It is preferred tool when it comes to task automation. It offers lots of bundled plugins for common tasks.

Features:

  • It makes the workflow as easy as writing a setup file
  • It allows automating repetitive tasks with minimum effort
  • It has a straightforward approach. It includes tasks in JS and config in JSON
  • Grunt includes built-in tasks for extending the functionality of plugins and scripts
  • It speeds up the development process and increase the performance of projects
  • The ecosystem of Grunt is huge; so it is possible to automate anything with very less effort
  • This web development tool reduces the chance of getting errors while performing repetitive tasks

Download link: https://gruntjs.com/

13. CodePen:

CodePen is a web development environment for front-end designers and developers. It is all about faster and smoother development. It allows to build, deploy website and build test cases.

Features:

  • It offers to build components to use elsewhere later
  • It includes some awesome features to write CSS faster.
  • Allows live view and live sync
  • Prefill API feature allows adding links and demo pages without need to code anything

Download link: https://codepen.io/

14. Jasmine:

Jasmine is a behavior-driven js for testing JavaScript code. It does not depend on any other JavaScript frameworks. This open source tool does not require a DOM.

Features:

  • Low overhead, no external dependencies
  • Comes out of the box with everything need to test code
  • Run browser tests and Node.js tests using the same framework

Download link: https://jasmine.github.io/index.html

15. Foundation:

Foundation is front-end framework for any device, medium, and accessibility. This responsive front-end framework makes it easy to design responsive websites, apps, and emails.

Features:

  • It offers the cleanest markup without sacrificing the utility and speed of Foundation
  • Possible to customize the build to include or remove certain elements. As it defines the size of columns, colors, font size.
  • Faster development and page load speed
  • Foundation is optimized truly for mobile devices
  • Customizability for developers of all levels
  • It takes responsive design to the next level, with the much-needed medium grid accommodating for tablets

Download link: http://foundation.zurb.com/sites/download.html/

16. Sublime Text:

Sublime Text is a proprietary cross-platform source code editor. This app development tool natively supports many programming languages and markup languages.

Features:

  • Command palette feature allows matching keyboard invocation of arbitrary commands
  • Simultaneous editing allows making the same interactive changes to multiple areas
  • Offers Python-based plugin API
  • Allows developers to give project specific preferences
  • Compatible with many language grammars from TextMate

Download link: https://www.sublimetext.com/

17. Less

Less is a pre-processor that extends the support for CSS language. It allows developers to use techniques to make CSS more maintainable and extendable.

Feature:

  • It can freely download and use
  • It offers higher-level style syntax, which allows web designers/developers to create advanced CSS
  • It easily compiles into standard CSS, before the web browser begins rendering a web page
  • Compiled CSS files can be uploaded to the production web server

Download link: http://lesscss.org/

18. Modaal:

Modal is front end development plugin which gives quality, flexibile, and accessibile modals.

Features:

  • Optimized for assistive technologies and screen readers
  • Fully responsive, scaling with browser width
  • Customizable CSS with SASS options
  • It offers full-screen and viewport mode
  • Keyboard control for gallery open and closing modal
  • Flexible close options and methods

Download link: https://github.com/humaan/Modaal

19. Github:

GitHub is a web development platform inspired by the way you work. This tool allows developers to review code, manage projects, and build software.

Features:

  • Coordinate easily, stay aligned, and get done with GitHub's project management tools
  • It offers right tools for the job
  • Easy documentation alongside quality coding
  • Allows all code in a single place
  • Developers can host their documentation directly from repositories

Download link: https://github.com/

Thank for reading!

Stack Data Structure In JavaScript for Developers

 Stack Data Structure In JavaScript for Developers

Let's learn about Stack data structure in JavaScript and how to implement Stack data structure with JavaScript using Arrays.

It's not a secret that we can use JS arrays as stacks, but how about implementing it from scratch? It's a good practice allowing us to better understand this data structure, use it in our daily job more thoughtfully and thus become better developers. Let's dive into it, but first, let's recall what the stack actually is.

Stack

The stack is the data structure that manages a collection of elements with two main operations:

  • push - which adds an element to the collection;

  • pop - which removes the most recently added element from the collection.

We can think of the stack as a physical stack of objects, for example, a stack of books. Let's remember this analogy, it will serve us later. The stack is also known as the LIFO (Last In First Out) structure - the last books we put on stack goes first out of it.

Big-O Analysis

Here is the big-O analysis for the main operations:

  • push(el) - O(1)

  • pop() - O(1)

But what is the big-O analysis for the operation of popping the first element in the stack. Let's recall our analogy with books and it becomes obvious that if we have a stack of N books then to be able to access the first book in the stack we have to pop the books from the top one by one N times. So, the big-O analysis for this operation is O(N).

Stack in JavaScript

Just as a quick reminder and the reference, we can use our well-known JS arrays as stacks. Let's use it to illustrate the situation with books stacking them first and unstacking then.

const stack = [];

// Stacking books one on top of another
stack.push("The Philosopher's Stone");
stack.push("The Chamber of Secrets");
stack.push("The Prisoner of Azkaban");
stack.push("The Goblet of Fire");
stack.push("The Order of the Phoenix");
stack.push("The Half-Blood Prince");
stack.push("The Deathly Hallows");

// Unstacking books
stack.pop(); // -> "The Deathly Hallows" | We put this book the last but take the first.
stack.pop(); // -> "The Half-Blood Prince" | We put this book the previous to the last so we take it second
Stack Implementation

Alright, let's implement the stack by our own. We don't want to emulate the implementation by creating a wrapper on top of the array. Instead, let's do it from scratch.

Preliminary Considerations

Let's figure out first what we'd like to achieve. In other words, let's imagine that we have already created the stack. How will we use it? Well, obviously, in the exact same manner as we did above:

const stack = new Stack();

// Stacking books one on top of another
stack.push("The Philosopher's Stone");
stack.push("The Chamber of Secrets");
stack.push("The Prisoner of Azkaban");
stack.push("The Goblet of Fire");
stack.push("The Order of the Phoenix");
stack.push("The Half-Blood Prince");
stack.push("The Deathly Hallows");

// Unstacking books
stack.pop(); // -> "The Deathly Hallows" | We put this book the last but take the first.
stack.pop(); // -> "The Half-Blood Prince" | We put this book the previous to the last so we take it second

Great! Now we have a clear vision what to implement. The next step is to figure out what will be the storage mechanism working under the hood of the stack. The good candidate is just a plain JS object.

And one more thing. How we're going to track the sequence of objects in the storage to popping up the objects in the correct order? Well, for the sake of it, it's quite enough to have just one internal pointer which point at the end of the stack.

The solution

Here is a possible solution:

function Stack() {
    const storage = {};
    let count = 0;

    function push(value) {
        storage[count++] = value;
    }

    function pop() {
        if (count === 0) {
            return null;
        }
        const v = storage[--count];
        delete storage[count];
        return v;
    }

    function getValues() {
        let i = 0,
            result = [];

        while (i < count) {
            result.push(storage[i]);
            i++;
        }

        return result;
    }

    return {
        push,
        pop,
        getValues,
        getLength: function() {
            return count;
        }
    };
}

Please notice that we added getValues() method returning a representation of the stack as an array from the first element in the stack to the last.

Unit Tests

For the sake of stability, let's cover our stack with unit tests (Jest is used).

const { Stack } = require("./stack");
let s, values;

beforeEach(() => {
    values = [
        "The Philosopher's Stone",
        "The Chamber of Secrets",
        "The Prisoner of Azkaban",
        "The Goblet of Fire",
        "The Order of the Phoenix",
        "The Half-Blood Prince",
        "The Deathly Hallows"
    ];
    s = new Stack();
    values.forEach(v => s.push(v));
});

test(`stack's 'push' method works`, () => {
    const sValues = s.getValues();

    values.forEach((v, index) => {
        expect(sValues[index]).toBe(values[index]);
    });
});

test(`stack's 'pop' method works`, () => {
    for (let i = values.length - 1; i >= 0; i--) {
        expect(values[i]).toBe(s.pop());
    }

    expect(s.pop()).toBeNull();
    expect(s.getValues().length).toBe(0);
});
Conclusion

Stacks are widely used in software development - it's used in IDEs to verify that open tag and parenthesis match their closing siblings, it's the basis for the QuickSort algorithm which is along with MergeSort are the top 2 popular sorting algorithms widely used in browsers and other systems. And by the way, every developer use it daily implicitly tracking the flow of the app in the call stack :). But the usage of the stack is much wider.

And now, seeing the importance of the stack, we can implement it from scratch in the plain JS. Isn't it great?

Happy coding!

Originally published by Egor Panok at egorpanok.com

Learn more