You are on page 1of 134

#PROGRAMMING: THE BASICS

# The Extended Version _

Programming fundamentals serve as the foundation for every programmer,


irrespective of their preferred programming language. This tutorial
aims to provide an easy overview of essential programming concepts and
principles that are crucial for beginners and even seasoned programmers
who may not have studied the fundamentals. By understanding these core
concepts, you will develop a strong programming mindset and be equipped
to tackle various programming challenges efficiently.

Section 1: Introduction to Programming

1.1 What is Programming?

Programming is the process of creating instructions for a computer to follow


in order to perform a specific task or solve a problem. Programmers write
code using programming languages, which are sets of rules and syntax that
computers can understand and execute. Programming enables us to build
software applications, websites, games, and much more.

1.2 Why Learn Programming Fundamentals?

Learning programming fundamentals is essential for several reasons:

● Problem Solving: Programming teaches you how to break down complex


problems into smaller, manageable tasks and develop algorithms to
solve them.
● Understanding Software: By learning programming fundamentals, you gain
a deeper understanding of how software works and how to design
efficient and scalable solutions.
● Adaptability: Strong programming fundamentals provide a solid
foundation that allows you to learn new programming languages and
technologies more easily.
● Debugging and Maintenance: Understanding programming fundamentals
helps you write clean, maintainable code and debug errors effectively.
● Collaboration: Having a shared understanding of programming
fundamentals enables effective collaboration with other programmers.

By: Waleed Mousa


Now, let's dive into the core concepts of programming fundamentals!

Section 2: Programming Paradigms


Programming Paradigms are the various styles or "ways" of programming.
These are some of the major ones:

2.1 Introduction to Programming Paradigms

A programming paradigm is a style or "way" of programming. Some


languages are designed to support one paradigm (Smalltalk supports
object-oriented programming, Haskell supports functional programming),
while others support multiple paradigms (such as Python, which is
object-oriented but also supports procedural and functional
programming).

2.2 Procedural Programming

In procedural programming, a program is divided into small parts called


procedures. Each procedure is a set of instructions that accomplishes a
specific task.

Procedural programming emphasizes a step by step set of instructions.


This paradigm is commonly used in languages like C, Go and earlier
versions of BASIC.

Example (in C):

#include <stdio.h>

void greet() {
printf("Hello, World!");
}

int main() {
greet(); // This is a procedure call
return 0;
}

Here, greet() is a procedure that prints "Hello, World!" when it's


called.

By: Waleed Mousa


2.3 Object-Oriented Programming (OOP)

OOP is a design philosophy which uses "objects" and classes for


designing applications and software. An object is a thing that can
perform a set of related activities. The set of activities that the
object performs defines the object's behavior.

OOP is supported in languages like Java, C++, and Python.

Example (in Python):

class Dog: # Define a class 'Dog'


def __init__(self, name):
self.name = name

def bark(self): # Define a method 'bark'


print(self.name + " says woof!")

fido = Dog("Fido") # Create an instance of 'Dog' named 'Fido'


fido.bark() # Call the 'bark' method

Here, Dog is a class, which is like a blueprint for creating objects.


fido is an instance (object) of the class Dog. The bark method
represents a behavior that the Dog class objects can perform.

2.4 Functional Programming

Functional programming is a paradigm where programs are constructed by


applying and composing functions. It is a declarative type of
programming style that focuses on what to solve rather than how to
solve (procedural programming).

Functional programming is supported in languages like Haskell, Erlang,


and in multi-paradigm languages like JavaScript.

Example (in JavaScript):

const add = (x, y) => x + y; // A simple function to add two numbers

const sum = add(5, 7); // Apply the function


console.log(sum); // Prints: 12

By: Waleed Mousa


Here, add is a function that takes two numbers and returns their sum.
The function is applied to the numbers 5 and 7.

2.5 Logic Programming

In logic programming, computation is performed by using a mechanism of


deduction. Instead of calling functions or procedures, you make
assertions about relations and the runtime attempts to prove those
assertions based on existing rules.

Prolog is one of the most commonly used logic programming languages.

Example (in Prolog):

loves(romeo, juliet).

loves(juliet, romeo) :- loves(romeo, juliet).

In this example, the loves(romeo, juliet) is an assertion or fact. The


second line is a rule that states "Juliet loves Romeo if Romeo loves
Juliet".

2.6 Comparison and Use Cases

Each paradigm has its strengths and weaknesses, and the choice of
paradigm can be influenced by other factors such as the specific problem
the program is trying to solve and the programming language in use.
Procedural programming can be useful for simple, straightforward tasks,
while object-oriented programming is widely used in large software
systems. Functional programming is becoming increasingly popular for
tasks that require concurrency or for programs with no side effects.
Logic programming is useful for tasks that involve complex rule systems
or inferences.

By: Waleed Mousa


Section 3: Algorithms and Problem Solving

3.1 Introduction to Algorithms

An algorithm is a step-by-step procedure or a set of rules for solving


a specific problem. It is the backbone of programming and helps us
design efficient solutions. Some common algorithm design techniques
include:

● Brute Force: Exhaustively checking all possible solutions.


● Divide and Conquer: Breaking a problem into smaller subproblems and
solving them individually.
● Greedy: Making locally optimal choices at each step to achieve an
overall optimal solution.
● Dynamic Programming: Breaking a problem into overlapping subproblems
and solving them once, storing the results for future use.
● Backtracking: Exploring all possible solutions by incrementally
building and undoing choices.

3.2 Problem-Solving Strategies

When faced with a programming problem, it's crucial to have effective


problem-solving strategies. Here are some strategies to consider:

● Understand the Problem: Clearly define the problem's requirements,


constraints, and expected outputs.
● Divide and Conquer: Break down the problem into smaller, manageable
parts.
● Pattern Recognition: Identify patterns, similarities, or recurring
themes in the problem.
● Algorithm Design: Devise an algorithmic approach to solve the problem
based on the identified patterns.
● Step-by-Step Execution: Implement the algorithm in your chosen
programming language, ensuring each step is correctly executed.
● Test and Debug: Verify your solution's correctness by testing it with
various inputs and debugging any errors or unexpected behaviors.

By: Waleed Mousa


3.3 Pseudocode and Flowcharts

Pseudocode and flowcharts are two essential tools for representing


algorithms visually and improving their readability.

● Pseudocode: Pseudocode is a simplified, high-level description of


an algorithm that uses a combination of natural language and
programming language constructs. It helps programmers plan and
structure their code before writing actual code in a specific
language.

Example of pseudocode for finding the sum of two numbers:

1. Start
2. Read the first number (num1)
3. Read the second number (num2)
4. Add num1 and num2 and store the result in sum
5. Display sum
6. Stop

● Flowcharts: Flowcharts use various shapes and arrows to represent


different steps and decisions in an algorithm. They provide a
visual representation of the flow of control within a program,
making it easier to understand and analyze.

Example of a flowchart for finding the sum of two numbers:

START -> Input num1 -> Input num2 -> Add num1 and num2 -> Store the result in
sum -> Display sum -> END

By: Waleed Mousa


Section 4: Variables and Data Types

4.1 Variables and Constants

In programming, variables are used to store and manipulate data. A


variable is a named location in computer memory that can hold a value.
Constants, on the other hand, are similar to variables but hold fixed
values that cannot be changed during program execution.

Variables and constants have a name and a data type associated with
them. The name allows us to refer to the variable, while the data type
defines the kind of data that the variable can hold.

4.2 Data Types

Different programming languages support various data types. Common data


types include:

● Integer: Represents whole numbers (e.g., 5, -2, 100).

● Floating-Point: Represents decimal numbers with fractional parts (e.g.,


3.14, -0.5, 2.0).

● String: Represents a sequence of characters (e.g., "Hello, World!",


"OpenAI").

● Boolean: Represents either true or false.

● Character: Represents a single character (e.g., 'A', '$', '#').

Programming languages also provide more advanced data types, such as


arrays, lists, structures, and classes, which allow you to store
multiple values or create custom data structures.

By: Waleed Mousa


4.3 Type Conversions and Casting

Sometimes, you may need to convert a value from one data type to
another. This is known as type conversion or casting. The two main
types of casting are implicit casting (also known as coercion) and
explicit casting.

● Implicit Casting: Occurs when the programming language


automatically converts one data type to another without explicit
instructions. For example, converting an integer to a
floating-point number.

● Explicit Casting: Requires the programmer to explicitly specify


the type conversion using type-casting operators or functions
provided by the programming language. This is useful when
converting between incompatible data types.

Example of explicit casting:

int num1 = 5;
float num2 = (float) num1; // Explicitly cast num1 to a float

Section 5: Control Structures

5.1 Conditional Statements

Conditional statements allow you to make decisions in your program


based on certain conditions. The common types of conditional statements
include:

● If Statement: Executes a block of code if a specified condition is


true.

if condition:
# Code to be executed if condition is true

By: Waleed Mousa


● If-else Statement: Executes a block of code if a condition is
true, and another block of code if the condition is false.

if condition:
# Code to be executed if condition is true
else:
# Code to be executed if condition is false

● Nested If-else Statement: Allows you to have multiple levels of


conditions.

if condition1:
# Code to be executed if condition1 is true
if condition2:
# Code to be executed if both condition1 and condition2 are true
else:
# Code to be executed if condition1 is true and condition2 is false
else:
# Code to be executed if condition1 is false

● Switch Statement (or Case Statement): Allows you to select one of


many code blocks to be executed based on the value of a variable
or an expression. This is available in some programming languages
like C++, Java, and JavaScript.

5.2 Looping Structures

Looping structures are used to repeat a block of code multiple times.


There are three common types of loops:

● For Loop: Executes a block of code for a specific number of


iterations, often used when the number of iterations is known in
advance.

for variable in sequence:


# Code to be executed for each value in the sequence

● While Loop: Executes a block of code repeatedly as long as a


specified condition is true, often used when the number of
iterations is not known in advance.

By: Waleed Mousa


while condition:
# Code to be executed while the condition is true

● Do-While Loop: Similar to the while loop, but it always executes


the block of code at least once before checking the condition.

do:
# Code to be executed
while condition

5.3 Control Flow Statements

Control flow statements allow you to control the flow of your program.
They include:

● Break Statement: Terminates the execution of a loop or a switch


statement and transfers control to the next statement outside the
loop or switch.
● Continue Statement: Skips the remaining code in the current
iteration of a loop and moves to the next iteration.
● Return Statement: Terminates the execution of a function and
returns a value (if specified) back to the caller.

Section 6: Functions and Modular Programming

6.1 Introduction to Functions

Functions are blocks of reusable code that perform specific tasks. They
allow you to break down your program into smaller, more manageable
parts, making your code modular and easier to understand. Functions can
take inputs (arguments) and produce outputs (return values).

By: Waleed Mousa


6.2 Function Declarations and Definitions

In most programming languages, functions are declared and defined using


the following syntax:

def function_name(parameters):
# Function code
return value # (optional)

● Function Name: A unique identifier that represents the function.


● Parameters: Variables that receive values when the function is
called. Parameters are optional and can be of any data type.
● Function Code: The block of code that is executed when the
function is called.
● Return Statement: Optional statement used to return a value from
the function. If omitted, the function may perform actions without
returning a value.

6.3 Function Parameters and Return Values

Functions can have zero or more parameters. Parameters allow you to


pass data into the function for processing. When a function is called,
the arguments provided are assigned to the corresponding parameters.

Example of a function with parameters and a return value:

def add_numbers(num1, num2):


sum = num1 + num2
return sum

Functions can also have no parameters or return values, depending on


the task they perform.

By: Waleed Mousa


6.4 Scope and Variable Visibility

Variables in programming have a scope, which determines where they can


be accessed within the program. The two main types of scope are global
scope and local scope.

● Global Scope: Variables declared outside any function are called


global variables and can be accessed from anywhere within the
program.
● Local Scope: Variables declared inside a function are called local
variables and can only be accessed within that specific function.

It's important to understand scope to avoid naming conflicts and ensure


proper variable visibility.

Section 7: Arrays and Lists

7.1 Introduction to Arrays and Lists

Arrays and lists are data structures that allow you to store multiple
values of the same or different data types. They provide a convenient
way to work with collections of data.

● Arrays: Arrays are fixed-size collections of elements, where each


element is identified by an index. The index represents the
position of the element in the array. Arrays are usually supported
in lower-level languages like C, C++, and Java.
● Lists: Lists are dynamic collections of elements, where elements
can be added or removed as needed. Lists are more flexible compared
to arrays and are supported in many programming languages,
including Python, JavaScript, and C#.

By: Waleed Mousa


7.2 Array/List Operations

Arrays and lists support various operations, including:

● Accessing Elements: Elements in an array or list can be accessed


using their index. The index starts at 0 for the first element.

my_list = [10, 20, 30, 40, 50]


print(my_list[0]) # Output: 10

● Modifying Elements: Elements in an array or list can be modified by


assigning a new value to the corresponding index.

my_list = [10, 20, 30, 40, 50]


my_list[2] = 35
print(my_list) # Output: [10, 20, 35, 40, 50]

● Appending Elements: Elements can be added to the end of a list


using the append() method or equivalent functions in other
programming languages.

my_list = [10, 20, 30]


my_list.append(40)
print(my_list) # Output: [10, 20, 30, 40]

● Slicing: Slicing allows you to extract a subset of elements from


an array or list by specifying a range of indices.

my_list = [10, 20, 30, 40, 50]


subset = my_list[1:4]
print(subset) # Output: [20, 30, 40]

7.3 Multidimensional Arrays

Multidimensional arrays are arrays that have multiple dimensions or


indices. They are useful for representing data in a tabular form or
when working with matrices.

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]


print(matrix[1][2]) # Output: 6

By: Waleed Mousa


7.4 Common Array/List Algorithms

Arrays and lists have a wide range of algorithms and operations that
can be performed on them, such as sorting, searching, and traversing.
Some common algorithms include:

● Sorting: Rearranging the elements in ascending or descending order


(e.g., bubble sort, insertion sort, merge sort, quicksort).
● Searching: Finding the position or existence of an element within
the array or list (e.g., linear search, binary search).
● Traversing: Visiting each element in the array or list (e.g.,
iterating over the elements using loops).

Understanding arrays and lists and their operations is crucial for


working with collections of data efficiently.

Section 8: Input and Output

8.1 User Input

User input allows programs to interact with users by accepting data


from them. Most programming languages provide built-in functions or
libraries to handle user input.

Example of user input in Python:

name = input("Enter your name: ")


age = int(input("Enter your age: ")) # Reading an integer input

8.2 Output to the Console

Outputting information to the console allows programs to display


messages, results, or other data to the user.

Example of console output in Python:

print("Hello, World!")

By: Waleed Mousa


8.3 File Input and Output

In addition to console input and output, programs can read from and
write to files. File input and output operations are essential for data
storage and retrieval.

Example of reading from a file in Python:

with open("filename.txt", "r") as file:


content = file.read()
print(content)

Example of writing to a file in Python:

with open("filename.txt", "w") as file:


file.write("Hello, World!")

Understanding input and output operations is crucial for building


interactive programs and handling data persistence.

Section 9: Error Handling and Exception Handling

9.1 Handling Errors and Exceptions

Errors and exceptions are inevitable in programming. Error handling and


exception handling techniques allow you to gracefully handle unexpected
situations and prevent program crashes.

● Errors: Errors occur when there are syntax or logical mistakes in


the code, preventing the program from running. These errors need
to be identified and fixed during the development process.
● Exceptions: Exceptions occur during program execution when an
unexpected condition or error arises. Exceptions can be
anticipated and handled using exception handling techniques.

By: Waleed Mousa


9.2 Error Types and Exception Classes

Exceptions are categorized into different types based on the nature of


the error. Common exception classes include:

● ArithmeticError: Occurs during arithmetic operations, such as


division by zero.
● TypeError: Occurs when an operation or function is applied to an
object of an inappropriate type.
● ValueError: Occurs when a function receives an argument of the
correct type but an invalid value.
● FileNotFoundError: Occurs when trying to open a file that does not
exist.
● IndexError: Occurs when trying to access an element at an invalid
index in a list or array.

Programming languages provide built-in exception classes, and you can


also create custom exception classes to handle specific situations in
your code.

9.3 Try-Catch Blocks

Try-catch blocks (also known as exception handling blocks) are used to


handle exceptions gracefully. The code within the try block is
executed, and if an exception occurs, it is caught by the corresponding
catch block.

Example of a try-catch block in Python:

try:
# Code that might raise an exception
except ExceptionType:
# Code to handle the exception

The catch block specifies the type of exception to catch. You can have
multiple except blocks to handle different types of exceptions or
provide a generic except block to handle any exception.

By: Waleed Mousa


9.4 Finally Block

Additionally, many programming languages provide a finally block, which


is executed regardless of whether an exception occurs or not. The
finally block is commonly used for cleanup tasks, such as closing files
or releasing resources.

Example of using a finally block in Python:

try:
# Code that might raise an exception
except ExceptionType:
# Code to handle the exception
finally:
# Code to be executed regardless of an exception

Error handling and exception handling are essential for building robust
and resilient programs that can gracefully handle unexpected
situations.

Section 10: Secure Coding


Secure coding is the practice of writing programs that are resistant to
attack by malicious or mischievous people or programs.

10.1 Importance of Secure Coding

Secure coding is important because today's software underpins systems


that perform critical functions in all sectors of the economy. Software
vulnerabilities can allow attackers to access sensitive data, disrupt
system operations, or gain control of systems.

10.2 Common Security Vulnerabilities

Some of the most common security vulnerabilities include:

● Buffer Overflows: Happen when a program writes more data to a


buffer than the buffer is able to hold.

By: Waleed Mousa


● Injection attacks: Occur when untrusted data is sent to an
interpreter as part of a command or query. The most common example
is SQL injection.
● Cross-Site Scripting (XSS): Occurs when an attacker injects
malicious scripts into websites viewed by other users.
● Insecure Direct Object References: Happens when a developer
exposes a reference to an internal implementation object.
● Misconfiguration: Can happen at any level of an application stack,
including the platform, web server, application server, database,
and framework.

10.3 Input Validation and Sanitization

Input validation is the process of ensuring an application operates on


clean, correct and useful data. It uses routines, rules, and
constraints that check for correctness, meaningfulness, and security of
input data. It can be used to detect unauthorized input before it is
processed by the application.

Example (Python):

def sanitize(input):
return ''.join(e for e in input if e.isalnum())

user_input = sanitize(input("Enter your name: "))


print(user_input)

In this example, we define a function sanitize that only allows


alphanumeric characters in the user input, potentially preventing
injection attacks.

10.4 Principle of Least Privilege

The principle of least privilege (POLP) is a computer security concept


in which a user is given the minimum levels of access necessary to
complete his/her job functions. This is done to enhance the protection
of data and functionality from faults and malicious behavior.

By: Waleed Mousa


10.5 Defense in Depth

Defense in Depth is an approach to cybersecurity in which a series of


defensive mechanisms are layered in order to protect valuable data and
information. If one mechanism fails, another steps up immediately to
thwart an attack.

10.6 Secure Error and Exception Handling

Secure error and exception handling is important to prevent an attacker


from learning too much about the architecture of your site through
detailed error messages.

Example (Python):

try:
risky_operation()
except Exception:
log_error("An error occurred.")
show_user_friendly_error_message()

In this example, if risky_operation() raises an exception, the error


details are logged but not displayed to the user, who only sees a
generic error message.

10.7 Secure Coding Practices and Standards

Some of the most widely accepted and effective secure coding practices
include:

● Input validation: Always validate user input to ensure it conforms


to the expected format, using both client-side and server-side
validation.
● Password protection: Always hash and salt passwords, and consider
adding additional security measures, like two-factor
authentication.
● Regular updates and patches: Always apply updates and patches to
your systems, frameworks, and libraries as soon as they become
available.

By: Waleed Mousa


● Use HTTPS: Always use HTTPS instead of HTTP to protect the
integrity and confidentiality of data in transit.
● Use of security headers: Use HTTP security headers to protect your
site from different types of attacks like XSS, code injection,
clickjacking, etc.

Section 11: Object-Oriented Programming (OOP) Basics

11.1 Introduction to OOP

Object-Oriented Programming (OOP) is a programming paradigm that


organizes code around objects, which are instances of classes. OOP
provides a way to structure programs, create reusable code, and model
real-world entities.

In OOP, objects have attributes (data) and behaviors


(methods/functions) associated with them. These attributes and
behaviors are defined by the class, which acts as a blueprint for
creating objects.

11.2 Classes and Objects

● Classes: A class is a blueprint or a template that defines the


attributes and behaviors of an object. It encapsulates data and
functions into a single entity.

Example of a class in Python:

class Circle:
def __init__(self, radius):
self.radius = radius

def calculate_area(self):
return 3.14 * self.radius * self.radius

● Objects: Objects are instances of a class. They are created using


the class blueprint and can access the attributes and behaviors
defined in the class.

By: Waleed Mousa


Example of creating objects from a class in Python:

circle1 = Circle(5) # Create an object of the Circle class with a radius of


5
circle2 = Circle(3) # Create another object with a radius of 3

print(circle1.calculate_area()) # Output: 78.5


print(circle2.calculate_area()) # Output: 28.26

11.3 Encapsulation, Inheritance, and Polymorphism

● Encapsulation: Encapsulation is the practice of bundling data and


the methods that operate on that data within a single unit (i.e.,
a class). It hides the internal details of how the data is stored
and manipulated, providing better data security and abstraction.
● Inheritance: Inheritance is a mechanism in which a class can
inherit attributes and behaviors from another class. The class
that is being inherited from is called the parent or base class,
and the class that inherits is called the child or derived class.
Inheritance promotes code reuse and allows for hierarchical
relationships between classes.

Example of inheritance in Python:

class Animal:
def __init__(self, name):
self.name = name

def sound(self):
pass # Placeholder method

class Dog(Animal):
def sound(self):
return "Woof!"

class Cat(Animal):
def sound(self):
return "Meow!"

dog = Dog("Buddy")
cat = Cat("Whiskers")

By: Waleed Mousa


print(dog.sound()) # Output: Woof!
print(cat.sound()) # Output: Meow!

● Polymorphism: Polymorphism allows objects of different classes to


be treated as objects of a common base class. This enables the use
of different implementations of methods based on the specific
object type. Polymorphism helps achieve code flexibility and
extensibility.

11.4 Constructors and Destructors

● Constructors: Constructors are special methods in a class that are


called when an object is created. They are used to initialize the
object's attributes and perform any necessary setup.

Example of a constructor in Python:

class Person:
def __init__(self, name, age):
self.name = name
self.age = age

person = Person("John", 25)

● Destructors: Destructors are special methods that are called when


an object is destroyed or goes out of scope. They are used to
perform cleanup tasks or release resources associated with the
object.

Destructor example in Python:

class Person:
def __init__(self, name):
self.name = name

def __del__(self):
print("Destructor called")

person = Person("John")
del person # Destructor called

By: Waleed Mousa


11.5 Abstraction and Interfaces

● Abstraction: Abstraction is the process of hiding unnecessary


details and exposing only essential information to the user. It
allows programmers to focus on high-level concepts without
worrying about the implementation details.
● Interfaces: An interface defines a contract that specifies a set of
methods that a class implementing the interface must provide.
Interfaces allow for loose coupling between components and promote
code modularity and flexibility.

Understanding object-oriented programming concepts empowers you to


build modular and maintainable code, model real-world scenarios
effectively, and promote code reusability.

Section 12: Regular Expressions


Regular expressions (regex) are sequences of characters that form a
search pattern. This pattern can be used to match, locate, and manage
text. Regular expressions can check if a string contains the specified
search pattern. They are extremely powerful and used in many fields,
including programming and computer science.

12.1 Basics of Regular Expressions

Here are some basic elements of regular expressions:

● Literal Characters: The most basic regular expression consists of


a single literal character; e.g. a.
● Metacharacters: Characters that have special meaning: . ^ $ * + ?
{ } [ ] \ | ( )
● Special Sequences: \d, \D, \s, \S, \w, \W, \b, \B etc. They make
some common patterns easier to write.
● Sets: A set is a set of characters inside a pair of square
brackets [] with a special meaning.

By: Waleed Mousa


For example, in Python:

import re

txt = "Hello, my phone number is 123-456-7890."


x = re.findall("\d", txt)
print(x) # prints ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']

In this example, we use \d to find all digit characters in a text.

12.2 Advanced Regular Expression Patterns

In addition to the basics, regular expressions can be very complex:

● Quantifiers: *, +, ?, {3}, {3,6} for specifying quantities of


characters.
● Grouping: Parentheses () for defining scope and logical groups.
● Pipe: | for OR operation.
● Escape special characters: Use \ before special characters.

For example, in Python:

import re

txt = "Hello, my phone number is 123-456-7890."


x = re.findall("\d{3}-\d{3}-\d{4}", txt)
print(x) # prints ['123-456-7890']

In this example, we use {3} to specify that we want to find exactly


three digits.

12.3 Use Cases of Regular Expressions

Regular expressions are used in search algorithms, search and replace


dialogs of text editors, input validators, parsers, and more. They can
be used in almost all programming languages like Python, JavaScript,
Java, etc.

Some common use cases include:

● Data Validation (e.g. check if a user input is a valid email or


phone number)

By: Waleed Mousa


● Data Scraping (e.g. extract all email addresses from a webpage)
● Text Manipulation (e.g. replace all occurrences of a specific
string)
● Syntax Highlighting in IDEs
● File renaming, searching, and so on.

Section 13: Concurrency and Multithreading


Concurrency and multithreading are fundamental concepts in computer
science that allow multiple sequences of operations to be executed in
overlapping periods of time.

13.1 Basics of Concurrency and Multithreading

Concurrency refers to the ability of different parts or units of a


program, algorithm, or problem to be executed out-of-order or in
partial order, without affecting the final outcome.

Multithreading is a specialized form of multitasking and a multitasking


is the feature that allows your computer to run two or more programs
concurrently. In general, there are two types of multitasking:
process-based and thread-based.

Example (Python):

import threading

def print_numbers():
for i in range(10):
print(i)

def print_letters():
for letter in 'abcdefghij':
print(letter)

thread1 = threading.Thread(target=print_numbers)
thread2 = threading.Thread(target=print_letters)

thread1.start()
thread2.start()

By: Waleed Mousa


thread1.join()
thread2.join()

In this example, two threads are created: one to print numbers from 0
to 9, and the other to print letters from 'a' to 'j'. The threads are
started with start(), and join() is used to make sure the program waits
for both threads to finish before it terminates.

13.2 Thread Synchronization

Thread synchronization is defined as a mechanism which ensures that two


or more concurrent threads do not simultaneously execute some
particular program segment known as critical section.

Example (Python):

import threading

class BankAccount:
def __init__(self):
self.balance = 100 # shared resource
self.lock = threading.Lock()

def update(self, change):


with self.lock:
self.balance += change

account = BankAccount()
thread1 = threading.Thread(target=account.update, args=[50])
thread2 = threading.Thread(target=account.update, args=[-75])

thread1.start()
thread2.start()

thread1.join()
thread2.join()

print(account.balance) # prints 75

In this example, a BankAccount object has a balance that is shared


between two threads. The update method, which modifies the balance, is a
critical section that must not be executed by multiple threads at the

By: Waleed Mousa


same time. This is ensured by acquiring a lock before modifying the
balance, and releasing the lock afterwards.

13.3 Deadlocks and Race Conditions

Deadlocks and race conditions are two of the main issues in concurrent
programming.

● Deadlock is a state in which each member of a group is waiting for


some other member to take action, such as sending a message or
more commonly releasing a lock.
● Race condition occurs when two or more operations must execute in
the correct order, but the program has not been written so that
this order is guaranteed to be maintained.

Section 14: Networking Basics


Networking is the practice of connecting computers and other devices
together to share resources.

14.1 Introduction to Networking

Networking is essential in today's computer world. With networking, you


can share resources like files, printers, and internet connections
between computers.

In a computer network, computers can be connected through cables, via


radio waves, and over telephone lines. Examples of computer networks
are the Internet, which connects millions of people globally, and Local
Area Networks (LANs), which might connect computers in your home,
school, or office.

14.2 Understanding HTTP/HTTPS

HTTP stands for Hypertext Transfer Protocol and is used for


transmitting hypermedia documents (like HTML) on the internet. It
defines commands and services used for transmitting webpage data.

By: Waleed Mousa


HTTPS, or HTTP Secure, is an extension of HTTP. It's used for secure
communication over a computer network and is widely used on the
internet. It encrypts the data being sent between the browser and the
server to prevent eavesdropping or tampering with the data.

14.3 Overview of TCP/IP

TCP/IP stands for Transmission Control Protocol/Internet Protocol,


which is a suite of communication protocols used to interconnect
network devices on the internet. TCP/IP can also be used as a
communications protocol in a private computer network (an intranet or
an extranet).

The entire Internet Protocol suite -- a set of rules and procedures --


is commonly referred to as TCP/IP. TCP and IP are the two main
protocols in the suite.

● TCP takes care of the data delivery, but it doesn't worry about
the packet structure or the control of the network.
● IP takes care of the routing of packets. It is responsible for
getting each packet of data to the right destination.

14.4 Web Sockets

WebSockets is a communication protocol that provides full-duplex


communication channels over a single TCP connection. It's used in web
applications to provide real-time functionality.

WebSocket is designed to be used in web browsers and web servers, but


it can be used by any client or server application. The WebSocket
Protocol is an independent TCP-based protocol.

Section 15: Databases and SQL


Databases are organized collections of data. SQL (Structured Query
Language) is a programming language used to manage and manipulate
databases.

By: Waleed Mousa


15.1 Introduction to Databases

At the core, a database is an organized collection of data. More


formally, it's a collection of related data entries that serves
multiple uses.

Databases are useful for storing information categorically. For


example, a company might have a database with the following tables:
"Employees", "Products", "Customers", and "Orders".

15.2 SQL Basics

SQL is a language designed to interact with data stored in a relational


database management system (RDBMS), or for stream processing in a
relational data stream management system (RDSMS). It is particularly
useful in handling structured data, i.e., data incorporating relations
among entities and variables.

Here's a very basic example of an SQL query:

SELECT * FROM Employees WHERE Salary > 50000;

This SQL query selects all fields from the "Employees" table where the
"Salary" field is greater than 50000.

15.3 Indexing and Normalization

Indexing in databases is a way to improve the performance of database


operations. An index is a data structure that improves the speed of
data retrieval operations on a database table.

Normalization is the process of efficiently organizing data in a


database. There are two goals of the normalization process: eliminating
redundant data (for example, storing the same data in more than one
table) and ensuring data dependencies make sense (only storing related
data in a table).

By: Waleed Mousa


15.4 Introduction to NoSQL

NoSQL databases are a type of database that can handle and sort all
kinds of data, everything from unstructured text documents to
structured data. NoSQL databases became popular as web applications
become more complex and required more diverse ways to store and query
data.

The four main types of NoSQL databases are:

1. Document databases: Store data in documents similar to JSON


(JavaScript Object Notation) objects. Each document can contain
pairs of fields and values.
2. Key-value stores: Every single item in the database is stored as
an attribute name (or 'key'), together with its value.
3. Wide-column stores: Instead of tables, you have column families,
which are containers for rows. Unlike relational databases, you
can have billions of columns, and rows don't have to have the same
columns.
4. Graph databases: Store data in nodes, which is the equivalent of a
record in a relational database, and edges, which represent
connections between nodes.

Section 16: Design Patterns


Design patterns are solutions to common problems in software design.
Each pattern is like a blueprint that you can customize to solve a
particular design problem in your code.

16.1 Introduction to Design Patterns

Design patterns can speed up the development process by providing


tested, proven development paradigms. Effective software design
requires considering issues that may not become visible until later in
the implementation.

Design patterns are categorized into three main groups: Creational,


Structural, and Behavioral patterns.
By: Waleed Mousa
16.2 Structural Design Patterns

Structural Design Patterns deal with object composition and typically


identify simple ways to realize relationships among entities.

1. Adapter Pattern: Allows classes with incompatible interfaces to


work together by wrapping its own interface around that of an
already existing class.
2. Decorator Pattern: Used to add new functionality to an existing
object, without being obtrusive.
3. Proxy Pattern: Used to provide a surrogate or placeholder for
another object to control access to it.

16.3 Behavioral Design Patterns

Behavioral patterns are concerned with algorithms and the assignment of


responsibilities between objects.

1. Observer Pattern: Defines a one-to-many dependency between objects


so that when one object changes state, all its dependents are
notified and updated automatically.
2. Strategy Pattern: Encapsulates an algorithm inside a class
separating the selection from the implementation.
3. State Pattern: Allows an object to alter its behavior when its
internal state changes.

16.4 Creational Design Patterns

Creational patterns deal with object creation mechanisms, trying to


create objects in a manner suitable to the situation.

1. Singleton Pattern: Ensures that a class only has one instance, and
provide a global point of access to it.
2. Factory Method Pattern: Defines an interface for creating an
object, but lets subclasses decide which class to instantiate.
3. Abstract Factory Pattern: Provides an interface for creating
families of related or dependent objects without specifying their
concrete classes.

By: Waleed Mousa


Design patterns can provide significant benefits, but they also have
their disadvantages. It's important to know them well enough to know
when to apply them and when to avoid them.

Section 17: Web Development Basics


Web development is the practice of creating or developing websites or
web pages hosted on the Internet or intranets.

17.1 Overview of Web Development

Web development is a broad term that involves the work of creating a


website for the Internet or an intranet (private network). It can range
from developing a simple single static page of plain text to complex
web applications, electronic businesses, and social network services.

Web development typically involves server-side scripting (backend),


client-side scripting (frontend), and database technology.

17.2 HTML Basics

HTML stands for Hyper Text Markup Language. It is used to describe the
structure of web pages using markup. HTML elements are the building
blocks of HTML pages and are represented by tags.

A very simple HTML document might look like this:

<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<h1>My First Heading</h1>
<p>My first paragraph.</p>
</body>
</html>

By: Waleed Mousa


17.3 CSS Basics

CSS stands for Cascading Style Sheets. It is used to control the style
and layout of multiple web pages all at once.

A simple example of CSS could look like this:

body {
background-color: lightblue;
}

h1 {
color: navy;
margin-left: 20px;
}

17.4 Introduction to JavaScript

JavaScript is a programming language that adds interactivity to your


website. This could be anything from a simple alert that pops up on
your screen to complex animations and 3D graphics.

Here's a basic example of JavaScript:

document.getElementById("demo").innerHTML = "Hello, World!";

17.5 Understanding Frontend and Backend Development

Frontend and backend development are two sides of the same coin. The
frontend of a web application is the part that users interact with.
It's built with languages like HTML, CSS, and JavaScript.

The backend of a web application is responsible for things like


calculations, business logic, database interactions, and performance.
Most of the code that is required to make an application work will be
done on the backend. Languages used for the backend include PHP, Ruby,
Python, Node.js, and more.

By: Waleed Mousa


Section 18: Scripting and Automation
Scripting and automation are powerful tools in the programmer's
toolkit. They can save time and effort by automating repetitive tasks,
and can also perform complex tasks with precision and consistency.

18.1 Introduction to Scripting and Automation

Scripting is a type of programming that is used to automate the


execution of tasks which could alternatively be executed one-by-one by
a human operator. Scripting languages are often interpreted (rather
than compiled), and tend to focus on automating common tasks.

Automation, as the name suggests, is the creation and application of


technologies to produce and deliver goods or services with minimal
human intervention. When applied to programming, it's about making your
software do as much work as possible, to save you having to do it!

18.2 Scripting Languages Overview

There are many scripting languages, each with its own strengths and
purposes. Here are a few examples:

● Bash: A Unix shell and command language, good for file manipulation
and program execution.
● Python: A high-level, interpreted language, Python is often used
for web and Internet development, scientific applications, data
analysis, and more.
● JavaScript: Primarily used in web development to add interactivity
to web pages.
● Ruby: A dynamic, open-source programming language with a focus on
simplicity and productivity. It has an elegant syntax that is easy
to read and write.
● Perl: A highly capable, feature-rich programming language with
over 30 years of development.

By: Waleed Mousa


18.3 Examples of Automation Scripts

Here's an example of a simple automation script in Python that renames


all files in a directory to lowercase:

import os

def rename_files():
file_list = os.listdir(r"<path to directory>")
os.chdir(r"<path to directory>")
for file_name in file_list:
os.rename(file_name, file_name.lower())

rename_files()

This script does the following:

1. Import the os module which provides a way of using operating


system dependent functionality.
2. Define a function called rename_files.
3. Get a list of all the files in a directory.
4. Change the current working directory to the specified path.
5. Iterate over every file in the directory and rename the file.

Section 19: Debugging and Testing

19.1 Debugging Techniques and Tools

Debugging is the process of identifying and fixing errors or bugs in


your code. Here are some techniques and tools to assist you in
debugging:

● Print Statements: Inserting print statements in your code to


display the values of variables at specific points can help track
the flow of execution and identify issues.

print(variable_name)

By: Waleed Mousa


● Debuggers: Debuggers are tools provided by programming
environments that allow you to step through your code, set
breakpoints, and inspect variables at runtime. They provide a more
interactive and detailed debugging experience.

import pdb
pdb.set_trace() # Set a breakpoint

● Logging: Logging libraries allow you to record information,


warnings, and errors during program execution. They provide a
systematic way to gather information and identify issues.

import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug("Debug message")
logging.error("Error message")

19.2 Writing Test Cases

Testing is a crucial part of the software development process. Writing


test cases helps ensure the correctness and reliability of your code.
Test cases typically fall into three categories:

● Unit Tests: Test individual units of code, such as functions or


methods, in isolation. They ensure that each unit performs as
expected.

import unittest

def add_numbers(a, b):


return a + b

class TestAddNumbers(unittest.TestCase):
def test_add_numbers(self):
result = add_numbers(2, 3)
self.assertEqual(result, 5)

if __name__ == '__main__':
unittest.main()

By: Waleed Mousa


● Integration Tests: Test the interaction and compatibility between
different units or components of your code. They ensure that the
units work correctly together.

import unittest

def multiply(a, b):


return a * b

class TestMultiply(unittest.TestCase):
def test_multiply(self):
result = multiply(2, 3)
self.assertEqual(result, 6)

if __name__ == '__main__':
unittest.main()

● System Tests: Test the entire system or application to validate


its behavior and functionality from end to end. They mimic
real-world scenarios and user interactions.

19.3 Unit Testing Basics

Unit testing is the process of testing individual units of code to


ensure they function correctly. Here are some basic principles of unit
testing:

● Test Coverage: Aim to test as many possible code paths and


scenarios as you can to achieve high test coverage. This helps
uncover potential issues and ensure the reliability of your code.
● Test Automation: Automate your tests whenever possible to enable
efficient and consistent testing. Automation frameworks, such as
unittest in Python, provide tools and assertions to simplify the
test-writing process.
● Test Isolation: Test each unit of code in isolation, without
dependencies on external factors or other units. This ensures that
any failures can be easily identified and isolated to the specific
unit being tested.

By: Waleed Mousa


Testing your code thoroughly helps identify and address issues early,
leading to more robust and stable software.

Section 20: Version Control and Collaboration

20.1 Introduction to Version Control Systems (VCS)

Version Control Systems (VCS) are tools that help manage changes to
source code and facilitate collaboration among developers. VCS allows
you to track modifications, revert to previous versions, and merge
changes made by multiple developers.

● Centralized VCS: In centralized VCS, there is a central repository


that stores the code, and developers check out and check in code
changes from that repository. Examples include CVS and Subversion
(SVN).
● Distributed VCS: Distributed VCS allows each developer to have a
complete copy of the code repository, including its history.
Developers can commit changes to their local repositories and
later synchronize with the main repository. Examples include Git
and Mercurial.

20.2 Repository Management

Version control systems manage code repositories, which store the code
and its history. Repository management involves tasks such as creating,
cloning, and managing branches.

● Creating a Repository: Initialize a new repository or create a new


one using the VCS command-line or graphical interface.

git init # Initialize a new Git repository

● Cloning a Repository: Clone an existing repository to create a


local copy of the codebase.

git clone <repository_url> # Clone a Git repository

By: Waleed Mousa


● Branching: Create branches to isolate changes and work on separate
features or fixes without affecting the main codebase.

git branch <branch_name> # Create a new branch


git checkout <branch_name> # Switch to a branch

20.3 Collaborative Development

Version control systems enable collaborative development, allowing


multiple developers to work together on the same codebase.
Collaboration involves tasks such as committing changes, merging code,
and resolving conflicts.

● Committing Changes: Save changes made to the code locally, with an


associated commit message explaining the changes.

git add <file_name> # Stage changes for commit


git commit -m "Commit message" # Commit changes

● Merging Code: Integrate changes from one branch into another,


combining the code modifications.

git merge <branch_name> # Merge changes from branch_name into the current
branch

● Resolving Conflicts: Conflicts may occur when merging if two or more


developers have made conflicting changes to the same code. Resolve
conflicts by manually editing the conflicting files and choosing the
correct changes.

# Git will mark conflicts in the file. Edit the file to resolve conflicts and
choose the desired changes.
git add <file_name> # Stage the resolved file
git commit -m "Merge conflict resolution" # Commit the merged changes

Collaborative development using version control systems allows for


efficient teamwork, easy code sharing, and effective code management.

By: Waleed Mousa


Section 21: Software Development Life Cycle (SDLC)

21.1 Overview of SDLC Phases

The Software Development Life Cycle (SDLC) is a framework that outlines


the process of developing software applications. SDLC consists of
several phases, each with its own objectives and activities. The common
phases include:

1. Requirements Gathering: Gathering and documenting the functional


and non-functional requirements of the software.
2. System Design: Creating a high-level design that outlines the
system architecture, modules, and their interactions.
3. Coding and Implementation: Writing the code for the software
application based on the design specifications.
4. Testing: Performing various testing activities to validate the
software and ensure it meets the requirements.
5. Deployment: Releasing the software to the production environment
for end-users to access and use.
6. Maintenance: Monitoring, maintaining, and enhancing the software
based on user feedback and changing requirements.

21.2 Agile vs. Waterfall Methodologies

There are different methodologies that can be followed within the SDLC.
Two popular approaches are Agile and Waterfall:

● Waterfall Methodology: The Waterfall methodology follows a


sequential, linear approach, where each phase is completed before
moving to the next one. It is suitable for projects with
well-defined and stable requirements.
● Agile Methodology: The Agile methodology emphasizes flexibility,
collaboration, and iterative development. It involves dividing the
project into sprints or iterations, allowing for continuous
feedback and adaptation to changing requirements.

By: Waleed Mousa


21.3 Project Management Tools

Project management tools help manage and track the progress of software
development projects. They provide features for task tracking, team
collaboration, and overall project management. Some popular project
management tools include:

● JIRA: JIRA is a versatile project management tool that supports


Agile methodologies. It allows for creating and tracking tasks,
managing backlogs, and visualizing project progress.
● Trello: Trello is a simple and user-friendly project management
tool that uses boards, lists, and cards to manage tasks and
collaborate with team members.
● Asana: Asana is a comprehensive project management tool that
offers features for task management, team collaboration, and
project tracking.

These tools provide a centralized platform for managing and organizing


project tasks, communication, and collaboration among team members.

Understanding the Software Development Life Cycle (SDLC) and utilizing


project management tools help streamline the development process,
ensure efficient project execution, and deliver high-quality software
applications.

By: Waleed Mousa


+100 JAVASCRIPT CONCEPTS
1- Variables: Variables are containers for storing data values. There
are three types of variables in JavaScript: var, let, and const.

let x = 5; // x is 5
const pi = 3.14; // pi is a constant 3.14

2- Data Types: JavaScript has several data types including Number,


String, Boolean, Object, Function, Null, and Undefined.

let number = 5; // Number


let string = "Hello"; // String

3- Arrays: Arrays are used to store multiple values in a single


variable.

let fruits = ["apple", "banana", "cherry"];

4- Objects: Objects are variables too. But objects can contain many
values.

let car = {type:"Fiat", model:"500", color:"white"};

5- Operators: JavaScript includes arithmetic operators, comparison


operators, bitwise operators, logical operators, assignment operators,
etc.

let x = 5;
let y = x + 2; // y is now 7

By: Waleed Mousa


6- Control Structures: Control structures help you handle the flow of
your program. This includes if, else, switch, for, while, do-while.

if (x > y) {
// do something
} else {
// do something else
}

7- Functions: Functions are blocks of code that can be defined, then


called at a later time, or in response to an event.

function myFunction(x, y) {
return x * y;
}

8- Events: JavaScript's interaction with HTML is handled through events


that occur when the user or browser manipulates a page.
html

<button onclick="myFunction()">Click me</button>

9- Strings and String Methods: Strings are useful for holding data that
can be represented in text form. There are many methods that can be
used with strings including length, indexOf, search, replace, etc.

let txt = "Hello World!";


let x = txt.length; // x is now 12

10- Number and Number Methods: JavaScript has only one type of number.
Numbers can be written with, or without decimals. JavaScript numbers
can also include + and - , and e to indicate exponents.

let x = 123e5; // x is 12300000


let y = 123e-5; // y is 0.00123

By: Waleed Mousa


11- Dates: JavaScript Date objects represent a single moment in time in
a platform-independent format. Date objects contain a Number that
represents milliseconds passed since the Unix Epoch.

let d = new Date();

12- JavaScript Math: JavaScript Math is a built-in object that has


properties and methods for mathematical constants and functions.

console.log(Math.PI); // 3.141592653589793
console.log(Math.sqrt(16)); // 4

13- Boolean Logic: Boolean is a datatype that returns either of two


values i.e., true or false.

let isCodingFun = true;


let isFishTasty = false;

14- Error Handling (try/catch/finally): JavaScript allows exception


handling via the try, catch, and finally blocks. try contains the code
to be run, catch catches any errors, and finally runs code regardless
of an error occurring or not.

try {
notAFunction();
} catch(err) {
console.log(err); // ReferenceError: notAFunction is not defined
} finally {
console.log('This will run regardless of the try/catch result');
}

15- Regular Expressions: Regular expression is an object that describes


a pattern of characters.

let patt = new RegExp("e");


let res = patt.test("The best things in life are free!");

By: Waleed Mousa


16- JSON: JSON (JavaScript Object Notation) is a lightweight data
interchange format that is easy for humans to read and write, and easy
for machines to parse and generate.

let text = '{"name":"John", "birth":"1986-12-14", "city":"New York"}';


let obj = JSON.parse(text);

17- AJAX: AJAX is about updating parts of a web page, without reloading
the whole page. It stands for Asynchronous JavaScript and XML.

let xhttp = new XMLHttpRequest();


xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
// Typical action to be performed when the document is ready
document.getElementById("demo").innerHTML = xhttp.responseText;
}
};
xhttp.open("GET", "filename", true);
xhttp.send();

18- Promises: A Promise is an object representing the eventual


completion or failure of an asynchronous operation.

let promise = new Promise(function(resolve, reject) {


// do a thing, possibly async, then...
if (/* everything turned out fine */) {
resolve("Stuff worked!");
} else {
reject(Error("It broke"));
}
});

19- Async/Await: async and await make promises easier to write.

async function example() {


let response = await fetch('https://api.github.com/users/github');
let user = await response.json();
return user;
}

By: Waleed Mousa


20- Closures: A closure is the combination of a function bundled
together (enclosed) with references to its surrounding state (the
lexical environment).

function makeAdder(x) {
return function(y) {
return x + y;
};
}
let add5 = makeAdder(5);
let add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12

21- Arrow Functions: Arrow functions allow for a shorter syntax when
writing functions. Arrow functions do not have their own this.

const square = x => x * x;

22- Template Literals: Template literals provide an easy way to


interpolate variables and expressions into strings.

let name = "John";


console.log(`Hello, ${name}!`); // "Hello, John!"

23- Spread Operator and Rest Parameters: The spread operator allows an
iterable to be expanded in places where zero or more arguments are
expected. The rest parameter syntax allows a function to accept an
indefinite number of arguments as an array.

// Spread operator
let arr1 = [1, 2, 3];
let arr2 = [...arr1, 4, 5, 6]; // [1, 2, 3, 4, 5, 6]

// Rest parameters
function sum(...theArgs) {
return theArgs.reduce((previous, current) => {
return previous + current;
});
}

By: Waleed Mousa


24- Destructuring Assignment: The destructuring assignment syntax is a
JavaScript expression that makes it possible to unpack values from
arrays, or properties from objects, into distinct variables.

let [a, b] = [1, 2];


console.log(a); // 1
console.log(b); // 2

25- Modules: JavaScript modules are a way to share and reuse code
across files.

// lib/math.js
export function sum(x, y) {
return x + y;
}

// some other file


import { sum } from './lib/math.js';
console.log(sum(1, 2)); // 3

26- Classes and Inheritance: Classes are a template for creating


objects. Inheritance is a way of creating a new class using methods and
properties of an existing class.

class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}

class Dog extends Animal {


speak() {
console.log(this.name + ' barks.');
}
}

By: Waleed Mousa


27- Symbols: Symbols are a new primitive type in JavaScript. Every
symbol value returned from Symbol() is unique.

let sym1 = Symbol();


let sym2 = Symbol("key"); // optional string key

28- Iterators and Generators: Iterators are objects that know how to
access items from a collection one at a time, while keeping track of
its current position within that sequence. Generators are a special
class of functions that simplify the task of writing iterators.

function* idMaker(){
let id = 0;
while(true) {
yield id++;
}
}

const numbers = [1, 2, 3, 4, 5];


const iterator = numbers[Symbol.iterator]();

console.log(iterator.next().value); // Output: 1
console.log(iterator.next().value); // Output: 2

29- Map, Filter, and Reduce: map, filter, and reduce are all array
methods in JavaScript that provide a functional programming style.

let numbers = [1, 2, 3, 4];


let doubled = numbers.map(item => item * 2);
let biggerThanTwo = numbers.filter(item => item > 2);
let sum = numbers.reduce((a, b) => a + b);

By: Waleed Mousa


30- Set and Map: Both Set and Map are newer built-in objects in
JavaScript. A Set object lets you store unique values of any type,
whether primitive values or object references. A Map object holds
key-value pairs and remembers the original insertion order of the keys.

let set = new Set();


set.add(1);
set.add('1'); // Different to 1 because it's a string.

let map = new Map();


map.set('name', 'John');
map.set('age', 25);

31- NaN: NaN is a special value that stands for "Not a Number". It is
used to indicate an undefined or unrepresentable value.

console.log(Math.sqrt(-1)); // NaN

32- Null and Undefined: Both null and undefined are special values in
JavaScript. undefined means a variable has been declared but has not
yet been assigned a value. null is an assignment value. It can be
assigned to a variable to represent no value or no object.

let test;
console.log(test); // undefined

test = null;
console.log(test); // null

33- Truthy and Falsy: Every value in JavaScript has an inherent boolean
value. When that value is evaluated in the context of a boolean
expression, we say that value is either truthy or falsy.

console.log(Boolean('')); // false - Empty string is falsy.


console.log(Boolean('Hello')); // true - Non-empty string is truthy.

By: Waleed Mousa


34- Global Object: In JavaScript, the global object is a special object
that contains all globally accessible functions and variables.

console.log(window.setTimeout); // function setTimeout() { [native code] }


console.log(Math.sqrt(4)); // 2

35- Type Coercion: Type coercion is the process of converting value


from one type to another (such as string to number, object to boolean,
and so on). It can be implicit or explicit.

let x = "5";
console.log(x + 1); // "51"
console.log(+x + 1); // 6

36- Scope and Hoisting: Scope is the accessibility or visibility of


variables, functions, and objects in some particular part of your code
during runtime. Hoisting is a JavaScript mechanism where variables and
function declarations are moved to the top of their containing scope.

console.log(x); // undefined - Due to hoisting


var x = 5;

37- Immutability: In JavaScript, const doesn't create an immutable


variable, but it does create a variable that can't be reassigned. For
arrays and objects, it means you can't reassign the entire object, but
you can mutate its properties.

const obj = { a: 1 };
obj.b = 2;
console.log(obj); // { a: 1, b: 2 }

By: Waleed Mousa


38- Callback Functions: A callback function is a function passed into
another function as an argument, which is then invoked inside the outer
function.

function greeting(name) {
console.log('Hello ' + name);
}
function processUserInput(callback) {
let name = prompt('Please enter your name.');
callback(name);
}
processUserInput(greeting);

39- Prototype and Inheritance: Prototypes are the mechanism by which


JavaScript objects inherit features from one another.

let animal = {
eats: true
};
let rabbit = Object.create(animal);
console.log(rabbit.eats); // true

40- Web APIs: Web APIs provide the functionality to create a dynamic,
interactive web application. These APIs include DOM manipulation, Fetch
API, Geolocation API, Web Storage, and more.

fetch('https://api.github.com/users/github')
.then(response => response.json())
.then(data => console.log(data));

41- this Keyword: this keyword refers to the object that is executing
the current function.

const person = {
name: 'John',
greet: function() { console.log('Hello, ' + this.name); }
};
person.greet(); // 'Hello, John'

By: Waleed Mousa


42- Timeouts and Intervals: setTimeout function is used to schedule
code execution after a designated amount of milliseconds. setInterval
is used to execute code repeatedly, starting after the interval of
time, then repeating continuously at that interval.

setTimeout(() => {
console.log('Runs after 2 seconds');
}, 2000);

setInterval(() => {
console.log('Runs every second');
}, 1000);

43- Bitwise Operators: Bitwise operators treat operands as a sequence


of 32 bits and allow you to manipulate individual bits in an operand.

let x = 5; // binary: 0101


let y = 1; // binary: 0001
let result = x & y; // binary: 0001, decimal: 1

44- Local Storage: Local Storage allows you to access a local Storage
object. Data stored persistently and isn't sent with every server
request.

localStorage.setItem('myKey', 'myValue');
let data = localStorage.getItem('myKey');
console.log(data); // 'myValue'

45- Session Storage: Session Storage allows you to add, modify, or


remove stored data which is saved temporarily and gets deleted after
the session ends (when the tab is closed).

sessionStorage.setItem('sessionKey', 'sessionValue');
let data = sessionStorage.getItem('sessionKey');
console.log(data); // 'sessionValue'

By: Waleed Mousa


46- Data Attributes: Data attributes let you assign custom data to an
element.

<div id="myDiv" data-my-attr="hello"></div>

<script>
let div = document.getElementById('myDiv');
let customData = div.dataset.myAttr;
console.log(customData); // 'hello'
</script>

47- Tagged Template Literals: Tagged templates allow you to parse


template literals with a function.

let a = 5;
let b = 10;

function tag(strings, ...values) {


console.log(strings[0]); // "Hello "
console.log(strings[1]); // " world "
console.log(values[0]); // 15
console.log(values[1]); // 50
}

tag`Hello ${a + b} world ${a * b}`;

48- IIFE (Immediately Invoked Function Expression): An IIFE is a


function that runs as soon as it is defined.

(function() {
console.log("This is an IIFE!");
})();

49- Strict Mode: Strict mode makes several changes to normal JavaScript
semantics. It eliminates some JavaScript silent errors by changing them
to throw errors.

'use strict';
x = 3.14; // This will cause an error because x is not defined

By: Waleed Mousa


50- Array methods (some, every, find): some checks if some elements pass
a test, every checks if all elements pass a test, find returns the
value of the first element that passes a test.

let array = [1, 2, 3, 4, 5];

let greaterThanFour = array.some(num => num > 4); // true


let allGreaterThanZero = array.every(num => num > 0); // true
let firstGreaterThanTwo = array.find(num => num > 2); // 3

51- Named function expressions: A named function expression is very


similar to a function declaration, except that it is created as a part
of an expression.

let myFunction = function func() {


console.log(func);
};
myFunction();

52- JavaScript Encoding/Decoding: encodeURI and decodeURI functions are


used to encode and decode a URI.

let uri = "my test.asp?name=ståle&car=saab";


let encoded = encodeURI(uri);
console.log(encoded); // my%20test.asp?name=st%C3%A5le&car=saab
console.log(decodeURI(encoded)); // my test.asp?name=ståle&car=saab

53- Default parameters: Default function parameters allow named


parameters to be initialized with default values if no value or
undefined is passed.

function multiply(a, b = 1) {
return a * b;
}
console.log(multiply(5, 2)); // 10
console.log(multiply(5)); // 5

By: Waleed Mousa


54- JavaScript Animation: JavaScript can be used to move elements
around on the page, create a slideshow, or other forms of animation.

let pos = 0;
let box = document.getElementById("animate");

let id = setInterval(frame, 5);


function frame() {
if (pos == 350) {
clearInterval(id);
} else {
pos++;
box.style.top = pos + "px";
box.style.left = pos + "px";
}
}

55- JavaScript BOM (Browser Object Model): The BOM allows JavaScript to
"talk to" the browser, it includes objects like navigator, history,
screen, location and document which is also the entry point into the
web page's content.

console.log(window.innerHeight); // inner height of the browser window

56- Web Workers: Web Workers are a simple means for web content to run
scripts in background threads.

let myWorker = new Worker("worker.js");


myWorker.postMessage([first.value, second.value]);
myWorker.onmessage = function(e) {
result.textContent = e.data;
}

57- Server Sent Events: Server-Sent Events (SSE) is a standard that


allows a web page to get updates from a server.

if(typeof(EventSource) !== "undefined") {


let source = new EventSource("demo_sse.php");
source.onmessage = function(event) {
document.getElementById("result").innerHTML += event.data + "<br>";
};
}

By: Waleed Mousa


58- Fetch API: The Fetch API provides a JavaScript interface for
accessing and manipulating HTTP requests and responses.

fetch('https://api.github.com/users/github')
.then(response => response.json())
.then(data => console.log(data));

59- Object Property Shorthand: In situations where the key and the
value that you're assigning to the key in the object you're creating
are the same, you can use a shorthand to create properties.

let name = 'John';


let age = 25;

let person = {name, age};


console.log(person); // {name: 'John', age: 25}

60- WeakMap: The WeakMap object is a collection of key/value pairs in


which the keys are weakly referenced. The keys must be objects and the
values can be arbitrary values.

let weakmap = new WeakMap();


let obj = {};
weakmap.set(obj, 'foo');
console.log(weakmap.get(obj)); // 'foo'

61- WeakSet: The WeakSet object lets you store weakly held objects in a
collection.

let weakSet = new WeakSet();


let obj = {};
weakSet.add(obj);
console.log(weakSet.has(obj)); // true

62- JavaScript Regular Expressions: A regular expression is a sequence


of characters that forms a search pattern. It's used for searching,
extracting, and replacing text.

let re = new RegExp('ab+c');


let reLiteral = /ab+c/;
console.log(re.test('abc')); // true
console.log(reLiteral.test('abc')); // true

By: Waleed Mousa


63- Proxies: Provide a way to wrap another object and intercept
operations, like reading/writing properties and others, optionally
handling them, or making them behave differently.

let target = {};


let proxy = new Proxy(target, {});

proxy.test = 5; // writing to proxy also writes to target


console.log(target.test); // 5
console.log(proxy.test); // 5

64- Reflect API: Provides methods for interceptable JavaScript


operations. The methods are the same as those of proxy handlers.

let obj = {};


Reflect.set(obj, 'prop', 'value');
console.log(obj.prop); // 'value'

65- Performance API: Provides access to performance-related information


enhanced with a high resolution timestamp.

const startTime = performance.now();

// The event to time goes here:

const endTime = performance.now();


console.log(`The event took ${endTime - startTime} milliseconds.`);

66- Async Iterators and Generators: They are enable the async functions
to be paused in the middle, one line at a time, and resumed only when a
value is ready, perfect for working with streams and other asynchronous
data sources.

async function* asyncGenerator() {


let i = 0;
while (i < 3) {
yield i++;
}
}

for await (let num of asyncGenerator()) {


console.log(num);
}

By: Waleed Mousa


67- BigInt: An arbitrary-precision integer.

const largeNumber = BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1);


console.log(largeNumber); // Output: 9007199254740992n

68- Optional chaining operator ?.: It allows to safely access nested


objects without checking presence of each of them.

let user = {}; // user has no address


console.log(user?.address?.street); // undefined (no error)

69- Nullish coalescing operator ??: It returns the first argument if


it’s not null/undefined. Otherwise, the second one.

let height = 0;
console.log(height ?? 100); // 0

70- Loop labels: A label allows us to break/continue outer loops from a


nested loop.

outer: for (let i = 0; i < 3; i++) {


for (let j = 0; j < 3; j++) {
let input = prompt(`Value at coords (${i},${j})`);
if (!input) break outer; // if an empty line or cancel, then break out of
both loops
}
}
console.log('Done!');

71- Custom Elements: Allows to define or customize web components.

class MyElement extends HTMLElement {


// element functionality goes here
}

customElements.define('my-element', MyElement);

72- Shadow DOM: Encapsulates style and structure for web components.

const shadowRoot = this.attachShadow({mode: 'open'});


const span = document.createElement('span');
span.textContent = 'Hello from the shadow!';
shadowRoot.appendChild(span);

By: Waleed Mousa


73- Function binding: The act of fixing a function’s context at
creation-time.

this.handleClick = this.handleClick.bind(this);

74- GlobalThis: A universal way to access the global this value (aka
global object) across environments.

console.log(globalThis.Math === Math); // true

75- Logical Assignment Operators: They perform a logical operation and


assignment in one step.

a ||= b; // OR and assignment


a &&= b; // AND and assignment
a ??= b; // Nullish Coalescing and assignment

76- Array at() method: Allows to get the element at a given index, with
support for negative indices.

let array = [1, 2, 3, 4, 5];


console.log(array.at(-1)); // 5

77- Numeric separators: Allows to use underscore as a separator in


numeric literals.

let billion = 1_000_000_000; // underscore as a separator


console.log(billion); // 1000000000

78- Top-level await: Allows to use await at the top-level of a module.

// top-level await is valid


const response = await fetch('...');

79- Pattern Matching Proposal: Allows to match and destructure data in


a deeper, more expressive way.

match (value) {
when ({ a: 1, b }) -> b
else -> throw new Error('not matched')
}

By: Waleed Mousa


80- Pipeline Operator Proposal: Allows to chain functions in a more
readable, functional manner.

// Using pipeline operator


let result = "hello" |> doubleSay |> capitalize |> exclaim;

81- Currying: Currying is the process of converting a function with


multiple arguments into a sequence of functions, each taking a single
argument.

function multiply(a) {
return function(b) {
return a * b;
};
}

var multiplyByTwo = multiply(2);


console.log(multiplyByTwo(4)); // Output: 8

82- Currying with lodash: The curry function from lodash can be used
for currying.

const _ = require('lodash');

function multiply(a, b, c) {
return a * b * c;
}

const curriedMultiply = _.curry(multiply);


console.log(curriedMultiply(2)(3)(4)); // Output: 24

83- Function composition: Function composition is combining multiple


functions to form a new function.

function add(a) {
return a + 1;
}

function multiply(b) {
return b * 2;
}

var composedFunction = (x) => multiply(add(x));

By: Waleed Mousa


console.log(composedFunction(3)); // Output: 8

84- Memoization: Memoization is a technique used to cache the results


of expensive function calls to improve performance.

function fibonacci(n, cache = {}) {


if (n in cache) {
return cache[n];
}

if (n <= 2) {
return 1;
}

const result = fibonacci(n - 1, cache) + fibonacci(n - 2, cache);


cache[n] = result;
return result;
}

console.log(fibonacci(10)); // Output: 55

85- Proxy traps: Proxy traps are the methods that can be defined on the
handler object to customize the behavior of the proxied object.

const handler = {
get(target, property) {
console.log(`Accessed ${property}`);
return target[property];
},
};

const proxy = new Proxy({}, handler);

console.log(proxy.name); // Output: Accessed name, undefined

By: Waleed Mousa


86- Function generators: Function generators are a combination of
generators and functions, allowing you to define reusable generator
functions.

function* generateNumbers() {
let number = 0;
while (true) {
yield number++;
}
}

const numberGenerator = generateNumbers();

console.log(numberGenerator.next().value); // Output: 0
console.log(numberGenerator.next().value); // Output: 1

87- Private class fields: Private class fields are class fields that are
scoped to the class and cannot be accessed outside of it.

class Person {
#name;

constructor(name) {
this.#name = name;
}

getName() {
return this.#name;
}
}

const person = new Person('John');

console.log(person.getName()); // Output: John


console.log(person.#name); // SyntaxError: Private field '#name' must be
declared in an enclosing class

By: Waleed Mousa


88- Optional chaining: Optional chaining allows you to access nested
properties of an object without worrying if any intermediate property
is null or undefined.

const user = {
name: 'John',
address: {
city: 'New York',
},
};

console.log(user.address?.city); // Output: New York


console.log(user.address?.country); // Output: undefined

89- Object spread syntax: Object spread syntax allows merging


properties from multiple objects into a new object.

const person = { name: 'John' };


const details = { age: 30, country: 'USA' };

const merged = { ...person, ...details };

console.log(merged); // Output: { name: 'John', age: 30, country: 'USA' }

90- Web Workers: Web Workers allow running JavaScript code in the
background, off the main thread, to improve performance and
responsiveness.

// Main thread
const worker = new Worker('worker.js');

worker.postMessage('Hello from the main thread!');

worker.onmessage = (event) => {


console.log(`Received: ${event.data}`);
};

// Worker thread (worker.js)


self.onmessage = (event) => {
console.log(`Received in the worker: ${event.data}`);
self.postMessage('Hello from the worker thread!');
};

By: Waleed Mousa


91- Proxied built-in objects: You can create proxies for built-in
objects like Array, Date, and Function to intercept and customize their
behavior.

const arrayProxy = new Proxy([], {


set(target, property, value) {
console.log(`Setting ${value} at index ${property}`);
return Reflect.set(target, property, value);
},
});

arrayProxy.push(1); // Output: Setting 1 at index 0

92- Custom iterable objects: You can create custom iterable objects by
implementing the iterator protocol.

const iterable = {
items: ['a', 'b', 'c'],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.items.length) {
return { value: this.items[index++], done: false };
}
return { done: true };
},
};
},
};

for (const item of iterable) {


console.log(item);
}

By: Waleed Mousa


93- Decorators: Decorators allow adding functionality to classes,
methods, and properties at design time.

function log(target, name, descriptor) {


const original = descriptor.value;

descriptor.value = function (...args) {


console.log(`Calling ${name} with arguments ${args}`);
return original.apply(this, args);
};

return descriptor;
}

class Calculator {
@log
add(a, b) {
return a + b;
}
}

const calc = new Calculator();


console.log(calc.add(2, 3)); // Output: Calling add with arguments 2,3, 5

94- Throttling: Throttling is a technique to limit the number of times


a function can be called within a specific time frame.

function throttle(func, limit) {


let inThrottle;
return function (...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}

function logMessage() {
console.log('Message logged');
}

const throttledLog = throttle(logMessage, 1000);


throttledLog(); // Output: Message logged

By: Waleed Mousa


throttledLog(); // (No output)

95- Debouncing: Debouncing is a technique to delay the execution of a


function until after a specific amount of time has passed without the
function being called again.

function debounce(func, delay) {


let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => func.apply(this, args), delay);
};
}

function saveData() {
console.log('Data saved');
}

const debouncedSave = debounce(saveData, 1000);


debouncedSave(); // (No output)
debouncedSave(); // (No output)
debouncedSave(); // Output: Data saved

96- Object.freeze: The Object.freeze method freezes an object, making


it immutable by preventing adding, modifying, or deleting properties.

const obj = {
name: 'John',
age: 30,
};

Object.freeze(obj);
obj.age = 40; // Assignment is ignored in strict mode or throws an error in
non-strict mode
console.log(obj.age); // Output: 30

By: Waleed Mousa


97- Object.seal: The Object.seal method seals an object, preventing the
addition or deletion of properties, but allowing the modification of
existing properties.

const obj = {
name: 'John',
age: 30,
};

Object.seal(obj);
delete obj.age; // Deletion is ignored
obj.name = 'Jane'; // Property can be modified
obj.gender = 'Male'; // Property addition is ignored

console.log(obj); // Output: { name: 'Jane', age: 30 }

98- Object.preventExtensions: The Object.preventExtensions method


prevents the addition of new properties to an object while allowing the
modification or deletion of existing properties.

const obj = {
name: 'John',
age: 30,
};

Object.preventExtensions(obj);
obj.name = 'Jane'; // Property can be modified
obj.gender = 'Male'; // Property addition is ignored

console.log(obj); // Output: { name: 'Jane', age: 30 }

99- FlatMap: The flatMap method combines the map and flat methods,
allowing mapping each element to a new array and then flattening the
resulting arrays into a single array.

const numbers = [1, 2, 3];

const result = numbers.flatMap((x) => [x, x * 2]);


console.log(result); // Output: [1, 2, 2, 4, 3, 6]

By: Waleed Mousa


100- Object.fromEntries: The Object.fromEntries method transforms a
list of key-value pairs into an object.

const entries = [
['name', 'John'],
['age', 30],
];

const obj = Object.fromEntries(entries);


console.log(obj); // Output: { name: 'John', age: 30 }

101- String.replaceAll: The replaceAll method replaces all occurrences


of a specified string or regular expression with another string.

const sentence = 'The quick brown fox jumps over the lazy dog.';
const newSentence = sentence.replaceAll('the', 'a');
console.log(newSentence); // Output: The quick brown fox jumps over a lazy
dog.

102- Object.hasOwn: The hasOwn method checks if an object has a


property directly defined on itself (not inherited from the prototype
chain).

const obj = {
name: 'John',
};

console.log(obj.hasOwn('name')); // Output: true


console.log(obj.hasOwn('toString')); // Output: false

103- Intl.ListFormat: The Intl.ListFormat object provides


language-sensitive formatting of lists.

const fruits = ['apple', 'banana', 'orange'];


const listFormat = new Intl.ListFormat('en', { style: 'long', type:
'conjunction' });
const formattedList = listFormat.format(fruits);
console.log(formattedList); // Output: apple, banana, and orange

By: Waleed Mousa


104- Intl.RelativeTimeFormat: The Intl.RelativeTimeFormat object
provides language-sensitive relative time formatting.

const timeFormat = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });


console.log(timeFormat.format(-2, 'day')); // Output: 2 days ago

105- File API: The File API provides a way to interact with files on the
user's device using JavaScript.

const input = document.getElementById('fileInput');

input.addEventListener('change', (event) => {


const file = event.target.files[0];
const reader = new FileReader();

reader.addEventListener('load', (event) => {


const contents = event.target.result;
console.log(contents);
});

reader.readAsText(file);
});

106- Intersection Observer API: The Intersection Observer API allows


detecting when an element enters or exits the viewport.

const element = document.getElementById('target');

const callback = (entries) => {


entries.forEach((entry) => {
console.log(entry.isIntersecting ? 'Element entered' : 'Element exited');
});
};

const options = {
root: null,
rootMargin: '0px',
threshold: 0.5,
};

const observer = new IntersectionObserver(callback, options);


observer.observe(element);

By: Waleed Mousa


#_Object-Oriented Programming (OOP) in JavaScript
Section 1: Introduction

1.1 What is Object-Oriented Programming?

Object-Oriented Programming (OOP) is a programming paradigm based on


the concept of "objects". Objects are data structures that contain
data, in the form of fields, often known as attributes; and code, in the
form of procedures, often known as methods. A distinguishing feature of
objects is that an object's procedures can access and often modify the
data fields of the object with which they are associated.

The OOP paradigm provides several benefits such as:

● Code reusability through inheritance.


● Simplification of complex problems through abstraction.
● Organized code through encapsulation.
● The ability to change behavior at runtime through polymorphism.

1.2 Why OOP in JavaScript?

JavaScript is a multi-paradigm language, meaning that it supports


programming in many different styles, including procedural,
object-oriented, and functional programming. OOP in JavaScript can be a
bit different from other languages because JavaScript is a
prototype-based language, not a class-based language.

However, as of ES6 (ECMAScript 2015), JavaScript introduced a class


syntax that allows developers to write classes and use OOP concepts in
a way that's closer to other OOP languages such as Java or C++.

OOP in JavaScript is useful because it:

● Organizes your code, which is especially helpful in large


applications.
● Makes your code more reusable and easier to maintain.

By: Waleed Mousa


● Encourages the DRY principle, which stands for "Don't Repeat
Yourself."

Despite the class syntax, JavaScript still uses prototypical


inheritance under the hood, which we'll explore in later sections.

Section 2: JavaScript Basics

Before we get into OOP with JavaScript, let's review some JavaScript
fundamentals. If you're already comfortable with these concepts, feel
free to move to the next section.

2.1 Syntax

In JavaScript, statements are terminated by semicolons, and blocks are


denoted by curly braces {}. Variables can be declared using var, let,
or const, each with different scoping rules.

let x = 10;
const y = 20;

2.2 Data Types

JavaScript has six primitive data types:

● String: represents a sequence of characters.


● Number: represents numeric values.
● Boolean: represents either true or false.
● undefined: represents an uninitialized variable.
● null: represents the intentional absence of value.
● Symbol: a unique and immutable primitive value.

In addition to these, JavaScript has the Object data type which can
store collections of data.

By: Waleed Mousa


2.3 Functions

Functions in JavaScript are blocks of code designed to perform a


particular task, defined using the function keyword:

function sayHello() {
console.log("Hello, world!");
}

2.4 Control Structures

JavaScript includes typical control structures including if-else


statements, switch statements, for loops, while loops, and do-while
loops.

Here's an example of a simple if-else statement:

if (x > y) {
console.log("x is greater than y");
} else {
console.log("x is not greater than y");
}

Now that we have some basic JavaScript understanding, let's start


exploring objects in JavaScript.

By: Waleed Mousa


Section 3: Objects in JavaScript

In JavaScript, objects are key-value pairs, also known as properties. A


property's value can be a function, in which case the property is known
as a method.

3.1 Object Creation

There are several ways to create objects in JavaScript.

Using object literal syntax:

let dog = {
name: "Spot",
breed: "Dalmatian",
age: 3,
bark: function() {
console.log("Woof!");
}
};

Using the new Object() syntax:

let dog = new Object();


dog.name = "Spot";
dog.breed = "Dalmatian";
dog.age = 3;
dog.bark = function() {
console.log("Woof!");
};

Using Object.create():

let dog = Object.create(Object.prototype, {


name: {
value: "Spot",
enumerable: true,
writable: true,
configurable: true
},
breed: {
value: "Dalmatian",

By: Waleed Mousa


enumerable: true,
writable: true,
configurable: true
},
age: {
value: 3,
enumerable: true,
writable: true,
configurable: true
}
});

dog.bark = function() {
console.log("Woof!");
};

Object.create() is a method in JavaScript that is used to create a new


object with the specified prototype object and properties.

Here's a breakdown of the Object.create() syntax:

Object.create(proto, [propertiesObject])

proto : This is a required argument, and it should be an object or


null. This will be set as the [[Prototype]] of the newly created
object.

propertiesObject (Optional) : This is an object where the keys


represent the names of the properties to be added to the new object,
and the values associated with those keys are property descriptors for
those properties.

Each property descriptor is an object with the following possible keys


(all optional):

● value: The value associated with the property (data descriptors


only). Defaults to undefined.
● writable: true if and only if the value associated with the
property may be changed (data descriptors only). Defaults to
false.

By: Waleed Mousa


● get: A function which serves as a getter for the property, or
undefined if there is no getter (accessor descriptors only).
● set: A function which serves as a setter for the property, or
undefined if there is no setter (accessor descriptors only).
● configurable: true if and only if the type of this property
descriptor may be changed and if the property may be deleted from
the corresponding object. Defaults to false.
● enumerable: true if and only if this property shows up during
enumeration of the properties on the corresponding object.
Defaults to false.

3.2 Accessing Properties and Methods

You can access properties and methods on an object using dot notation
or bracket notation:

console.log(dog.name); // outputs: "Spot"


dog.bark(); // outputs: "Woof!"

// Or using bracket notation


console.log(dog["name"]); // outputs: "Spot"
dog["bark"](); // outputs: "Woof!"

3.3 Adding and Deleting Properties

You can add new properties to an object simply by assigning a value to


them:

dog.color = "white with black spots";


console.log(dog.color); // outputs: "white with black spots"

And you can delete properties from an object using the delete keyword:

delete dog.age;
console.log(dog.age); // outputs: undefined

That's the basic idea of objects in JavaScript! In the next section,


we'll explore more complex topics, like constructor functions and
prototypes.

By: Waleed Mousa


Section 4: JavaScript Constructor Functions

In JavaScript, a constructor function is a special kind of function


that is used to create objects of a particular type. The name of a
constructor function usually starts with a capital letter to
distinguish it from regular functions.

4.1 What is a constructor?

A constructor is a function that initializes an object. In JavaScript,


the constructor method is called when an object is created.

4.2 Creating objects with constructors

Let's create a constructor function for a Dog object:

function Dog(name, breed, age) {


this.name = name;
this.breed = breed;
this.age = age;
this.bark = function() {
console.log(this.name + " says woof!");
};
}

You can then create a new Dog object using the new keyword:

let myDog = new Dog("Spot", "Dalmatian", 3);


myDog.bark(); // Outputs: "Spot says woof!"

4.3 Adding properties and methods to objects

You can add a property or method to an object after it's been


constructed. Here's how you could add a color property and a birthday
method to the myDog object:

myDog.color = "Black and White";


myDog.getBirthday = function() {
return new Date().getFullYear() - this.age;
};
console.log(myDog.color); // Outputs: "Black and White"
console.log(myDog.getBirthday()); // Outputs the birth year of the dog

By: Waleed Mousa


Section 5: Prototypal Inheritance in JavaScript

Inheritance is a core concept in object-oriented programming. It allows


classes or objects to inherit properties and methods from another,
promoting code reuse and modularity.

In JavaScript, inheritance is prototype-based. When an object is


created, it inherits the properties and methods from its prototype.
Here's a quick example:

let animal = {
species: "animal",
describe: function() {
return `This is a ${this.species}.`;
}
};

let dog = Object.create(animal);


dog.species = "dog";

console.log(dog.describe()); // Outputs: "This is a dog."

In this example, dog is created with animal as its prototype, so it


inherits the describe method from animal.

5.1 Inheritance with Constructor Functions

We can also use constructor functions to create a form of inheritance.


To do this, we'll use the call() function, which allows us to call a
function with a given this value and arguments provided individually.

function Animal(species) {
this.species = species;
}

Animal.prototype.describe = function() {
return `This is a ${this.species}.`;
};

By: Waleed Mousa


function Dog(name) {
Animal.call(this, "dog");
this.name = name;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function() {
return `${this.name} says woof!`;
};

let myDog = new Dog("Spot");

console.log(myDog.describe()); // Outputs: "This is a dog."


console.log(myDog.bark()); // Outputs: "Spot says woof!"

In this example, Dog is a "subclass" of Animal and inherits its


describe method. The Dog constructor calls the Animal constructor to
initialize the species property, and the Dog prototype is set to a new
object created from the Animal prototype. This allows Dog instances to
inherit Animal methods.

Then we add a bark method to the Dog prototype. This method is specific
to Dog instances and is not shared by Animal instances.

The line Dog.prototype.constructor = Dog; is needed because setting


Dog.prototype to Object.create(Animal.prototype) makes
Dog.prototype.constructor point to Animal. We want
Dog.prototype.constructor to correctly point to Dog, so we set it
explicitly.

By: Waleed Mousa


Section 6: ES6 Classes

While JavaScript is prototype-based, ECMAScript 6 (ES6) introduced a


class syntax that allows you to write code that looks more like
traditional class-based languages. Under the hood, this syntax is just
syntactic sugar over JavaScript's existing prototype-based inheritance.
It does not change the core of how object-oriented programming works in
JavaScript. It just provides a cleaner, more elegant syntax for
creating objects and dealing with inheritance.

6.1 Defining classes

Here is a basic example of how to define a class in JavaScript:

class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}

area() {
return this.height * this.width;
}
}

const myRectangle = new Rectangle(5, 10);


console.log(myRectangle.area()); // Outputs: 50

In this example, Rectangle is defined as a class with a constructor and


a method named area. The constructor is a special method that is
automatically called when a new instance of the class is created.

6.2 Inheritance with classes

Inheritance can be implemented in ES6 classes using the extends


keyword. Here's an example:

By: Waleed Mousa


class Animal {
constructor(name) {
this.name = name;
}

speak() {
console.log(this.name + ' makes a noise.');
}
}

class Dog extends Animal {


speak() {
console.log(this.name + ' barks.');
}
}

let dog = new Dog('Rover');


dog.speak(); // Outputs: "Rover barks."

In this example, Dog is a subclass of Animal and inherits its


constructor. The speak method is overridden in the Dog class, so Dog
instances will use this version of the method.

6.3 Using super

The super keyword can be used in the constructor of a subclass to call


the constructor of the superclass. Here's an example:

class Animal {
constructor(name) {
this.name = name;
}

speak() {
console.log(this.name + ' makes a noise.');
}
}

class Dog extends Animal {


constructor(name, breed) {
super(name);
this.breed = breed;
}

By: Waleed Mousa


speak() {
super.speak();
console.log(this.name + ' barks.');
}
}

let dog = new Dog('Rover', 'Retriever');


dog.speak(); // Outputs: "Rover makes a noise." and then "Rover barks."

In this example, the Dog constructor calls super(name) to run the


Animal constructor, then adds a breed property. The speak method also
calls super.speak() to run the Animal version of the method before
adding its own additional behavior.

Section 7: Encapsulation

Encapsulation is one of the fundamental concepts in object-oriented


programming. It refers to the bundling of data, and the methods that
act on that data, into a single unit, and restricting direct access to
it. In JavaScript, encapsulation can be achieved using closures, ES6
classes with constructor function and getter/setter methods.

7.1 Encapsulation using Closures

Here is an example:

function createCar(make, model) {


let odometer = 0;
return {
make,
model,
drive(distance) {
odometer += distance;
},
readOdometer() {
return odometer;
}
};

By: Waleed Mousa


}

const myCar = createCar('Toyota', 'Corolla');


myCar.drive(50);
console.log(myCar.readOdometer()); // Outputs: 50

In this example, odometer is private data encapsulated within the


createCar function. It can't be accessed or modified directly from
outside that function. Instead, it can only be modified through the
drive method, and its current value can be read with the readOdometer
method.

7.2 Encapsulation using ES6 Classes

With the introduction of ES6 classes, JavaScript now has built-in


syntax for defining getter and setter methods, which can be used to
create private properties and control how they are accessed and
modified. Here's an example:

class Car {
constructor(make, model) {
this._make = make;
this._model = model;
this._odometer = 0;
}

get make() {
return this._make;
}

get model() {
return this._model;
}

drive(distance) {
this._odometer += distance;
}

readOdometer() {
return this._odometer;
}
}

By: Waleed Mousa


const myCar = new Car('Toyota', 'Corolla');
myCar.drive(50);
console.log(myCar.readOdometer()); // Outputs: 50

In this example, the underscore prefix (_) on _odometer, _make, and


_model is a common convention to indicate that they are intended to be
private properties. The drive method and readOdometer method are the
only ways to modify and read the _odometer property. The make and model
properties can only be read and not modified.

Keep in mind that this isn't true privacy. The properties can still be
accessed and modified directly, and the underscore is just a convention
to signal that they shouldn't be. ES6 introduced a new feature,
JavaScript #private fields, to make properties truly private.

#_ Overview of JavaScript #private fields:

JavaScript introduced a new syntax for truly private class fields,


denoted by a # sign before the field name. This syntax ensures that
these fields are only accessible within the class they're defined.
They're not accessible outside of the class, not even from instances of
the class. This provides a stronger level of encapsulation compared to
the underscore (_) convention.

Here's an example using private fields in the BankAccount class:

class BankAccount {
#balance;

constructor(initialDeposit) {
this.#balance = initialDeposit;
}

deposit(amount) {
this.#balance += amount;
}

withdraw(amount) {
if (this.#balance >= amount) {
this.#balance -= amount;

By: Waleed Mousa


} else {
console.log("Insufficient funds for this withdrawal.");
}
}

getBalance() {
return this.#balance;
}
}

let account = new BankAccount(100);


account.deposit(50);
account.withdraw(30);
console.log(account.getBalance()); // Outputs: 120
console.log(account.#balance); // Error: Private field '#balance' must be
declared in an enclosing class

As you can see in this example, attempting to access the #balance field
directly from outside the class results in a syntax error. This ensures
that the #balance field can only be accessed or modified through the
deposit, withdraw, and getBalance methods.

Section 8: Polymorphism

Polymorphism is a concept in OOP that describes the ability of objects


of different classes to respond to the same method call in different
ways. In other words, the same method can have different behaviors in
different classes. In JavaScript, polymorphism is achieved through
inheritance and method overriding.

8.1 Method Overriding

In the context of OOP in JavaScript, method overriding occurs when a


method in a child class has the same name as one in its parent class.
When the method is called on an instance of the child class, the child
class's version of the method is executed.

Here's an example:

By: Waleed Mousa


class Animal {
makeSound() {
console.log('The animal makes a sound');
}
}

class Dog extends Animal {


makeSound() {
console.log('The dog barks');
}
}

class Cat extends Animal {


makeSound() {
console.log('The cat meows');
}
}

let dog = new Dog();


dog.makeSound(); // Outputs: 'The dog barks'

let cat = new Cat();


cat.makeSound(); // Outputs: 'The cat meows'

In this example, the Dog and Cat classes override the makeSound method
of the Animal class. When makeSound is called on a Dog or Cat object,
the overridden method in the respective child class is executed.

8.2 Super Keyword

Sometimes, you might want to execute the parent class's method before
or after executing additional code in the child class's method. You can
do this using the super keyword.

Here's an example:

class Dog extends Animal {


makeSound() {
super.makeSound();
console.log('The dog barks');
}
}

By: Waleed Mousa


let dog = new Dog();
dog.makeSound(); // Outputs: 'The animal makes a sound' then 'The dog barks'

In this example, super.makeSound() calls the makeSound method of the


Animal class. Then, the additional code in the Dog class's makeSound
method is executed.

Section 9: Abstraction

Abstraction is a principle in object-oriented programming that deals


with ideas rather than events. It is the process of exposing only the
relevant and essential data to the users without showing unnecessary
information. In JavaScript, abstraction can be achieved through
classes, interfaces, and methods.

9.1 Abstraction with Classes

Abstraction can be achieved in JavaScript using classes. You can define


a class that abstracts a certain concept or object, and then use that
class throughout your code.

class Vehicle {
constructor(name, type) {
this.name = name;
this.type = type;
}

start() {
return `${this.name} engine started`;
}
}

let vehicle = new Vehicle("Tesla", "Electric");


console.log(vehicle.start()); // Outputs: "Tesla engine started"

By: Waleed Mousa


In this example, the Vehicle class is an abstraction of a vehicle. It
has properties like name and type, and methods like start. This class
can be used to create any type of vehicle and start it.

9.2 Abstraction with Methods

Another way to achieve abstraction in JavaScript is by providing


methods that perform complex operations behind the scenes, while
presenting a simple interface to the user.

class BankAccount {
constructor(balance = 0) {
this.balance = balance;
}

deposit(amount) {
this.balance += amount;
return this.balance;
}

withdraw(amount) {
if (amount > this.balance) {
return "Insufficient funds";
} else {
this.balance -= amount;
return this.balance;
}
}
}

let account = new BankAccount(100);


console.log(account.deposit(50)); // Outputs: 150
console.log(account.withdraw(30)); // Outputs: 120

In this example, the deposit and withdraw methods of the BankAccount


class are abstractions. They perform the necessary calculations to
update the balance of the account, and the user doesn't need to know
the details of how they work in order to use them.

By: Waleed Mousa


Section 10: Advanced Topics

10.1 Composition vs Inheritance

Composition and inheritance are two ways to reuse code across objects
in JavaScript.

Inheritance involves creating a parent class that contains shared code,


and then creating child classes that inherit this code. We've discussed
this extensively in the previous sections. One downside to inheritance
is that it can lead to tightly coupled code and can be difficult to
manage as classes grow and start to have different requirements.

Composition, on the other hand, is a way of building complex objects by


combining simpler ones. In JavaScript, composition can be achieved by
adding functions to the prototype of an object, or by using
Object.assign() to combine objects.

Here's an example of composition:

const canEat = {
eat: function() {
console.log("Eating");
}
};

const canWalk = {
walk: function() {
console.log("Walking");
}
};

const person = Object.assign({}, canEat, canWalk);

person.eat(); // Outputs: "Eating"


person.walk(); // Outputs: "Walking"

In this example, we're composing a person object from the canEat and
canWalk objects, rather than inheriting from a parent class.

By: Waleed Mousa


10.2 Prototype vs Class Inheritance

In JavaScript, you can achieve inheritance in two ways: through


prototype chains or through class inheritance introduced in ES6.

Prototype inheritance involves creating an object that serves as a


prototype, and then creating new objects that inherit properties and
methods from this prototype.

Class inheritance, on the other hand, involves creating a parent class


that contains shared properties and methods, and then creating child
classes that inherit from this parent class.

In reality, class inheritance is built on top of prototype inheritance.


When you create a class and use the extends keyword, JavaScript sets up
a prototype chain under the hood.

10.3 Factory Functions vs Constructor Functions vs Classes

Factory functions, constructor functions, and classes are all ways to


create objects in JavaScript.

A factory function is a function that returns an object when you call


it. Here's an example:

function createPerson(name, age) {


return {
name: name,
age: age
};
}

let person = createPerson("Alice", 25);

By: Waleed Mousa


A constructor function is a function that is used with the new keyword
to create new objects. Here's an example:

function Person(name, age) {


this.name = name;
this.age = age;
}

let person = new Person("Alice", 25);

A class, as you've seen in the previous sections, is a way to define a


blueprint for creating objects. Here's an example:

class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}

let person = new Person("Alice", 25);

In each case, we're achieving the same end result: we're creating a
person object with a name and age. The method you choose to create
objects really depends on your specific needs and the conventions of
your codebase.

Section 11: Modules

Modules in JavaScript are reusable pieces of code that can be exported


from one program and imported for use in another program. They allow
you to encapsulate code into separate files, organize related code
together, and avoid polluting the global namespace.

JavaScript didn't originally have built-in support for modules, but


with the introduction of ES6 (ES2015), JavaScript now natively supports
module functionality. Here's how you can create and use modules in
modern JavaScript:

By: Waleed Mousa


1.1 Exporting from a Module

You can use the export keyword to export functions, objects, or


primitive values from a module so that they can be used in other
programs. You can use named exports or a default export.

Named export (allows multiple per module):

// mathFunctions.js
export function add(x, y) {
return x + y;
}

export function subtract(x, y) {


return x - y;
}

Default export (only one per module):

// MyModule.js
export default function() { console.log("I'm the default export!"); }

1.2 Importing from a Module

You can use the import keyword to import functions, objects, or values
that were exported from another module.

Import named exports:

// main.js
import { add, subtract } from './mathFunctions.js';

console.log(add(2, 2)); // 4
console.log(subtract(2, 2)); // 0

Import default export:

// main.js
import myDefaultFunction from './MyModule.js';

myDefaultFunction(); // I'm the default export!

By: Waleed Mousa


#_ Getting Started with Design Patterns in JavaScript
Chapter 1: Introduction to Design Patterns

Design patterns are reusable solutions to common problems that occur in


software design. They represent best practices and can speed up the
development process by providing proven ways of structuring and
relating entities in your software.

Design patterns can be classified into three categories:

1. Creational Patterns: Deal with object creation mechanisms, trying


to create objects in a manner suitable to the situation.
2. Structural Patterns: Deal with the composition of classes or
objects, ensuring that the entities work together as intended.
3. Behavioral Patterns: Deal with communication between objects, how
they interact and distribute responsibilities.

Design patterns are not to be memorized and forced into your code.
Instead, they should be considered tools that can be used when they are
needed. They are language-independent strategies for solving common
object-oriented design problems. That means you can use them in almost
any language that supports object-oriented programming concepts.

Chapter 2: Creational Design Patterns

Creational design patterns deal with object creation mechanisms, trying


to create objects in a way that is best suited to a particular
situation. The basic form of object creation could result in design
problems or added complexity to the design. Creational design patterns
solve this problem by controlling the object creation process.

By: Waleed Mousa


Here are the Creational Patterns we'll cover:

1. Singleton Pattern
2. Factory Pattern
3. Abstract Factory Pattern
4. Builder Pattern
5. Prototype Pattern

1. Singleton Pattern

The Singleton pattern is a design pattern that restricts the


instantiation of a class to a single object. This is useful when
exactly one object is needed to coordinate actions across the system.

In JavaScript, we can create a Singleton like this:

let instance = null;

class Singleton {
constructor() {
if (!instance) {
instance = this;
}

// Initialize your object


this.time = new Date();

return instance;
}
}

const singleton1 = new Singleton();


const singleton2 = new Singleton();

console.log(singleton1 === singleton2); // true

By: Waleed Mousa


2. Factory Pattern

The Factory pattern is a creational pattern that provides an interface


for creating objects in a superclass, but allows subclasses to alter
the type of objects that will be created.

class CarFactory {
createCar(model) {
if (model === 'Yaris') return new Yaris();
if (model === 'Prius') return new Prius();
return null;
}
}

class Yaris {
info() {
return "This is a Yaris";
}
}

class Prius {
info() {
return "This is a Prius";
}
}

const factory = new CarFactory();


const car = factory.createCar('Yaris');

console.log(car.info()); // This is a Yaris

3. Abstract Factory Pattern

The Abstract Factory pattern is quite similar to the Factory Pattern.


It's a creational pattern that provides a way to encapsulate a group of
individual factories that have a common theme without specifying their
concrete classes. In normal usage, the client software creates a
concrete implementation of the abstract factory and then uses the
generic interface of the factory to create the concrete objects that
are part of the theme.

Here's a simple example:

By: Waleed Mousa


class AbstractVehicleFactory {
constructor() {
this.types = {};
}

getVehicle(type, options) {
const Vehicle = this.types[type];
return (Vehicle) ? new Vehicle(options) : null;
}

registerVehicle(type, Vehicle) {
const proto = Vehicle.prototype;

// only register classes that fulfill the Vehicle contract


if (proto.drive && proto.breakDown) {
this.types[type] = Vehicle;
}

return this;
}
}

class Car {
constructor(options) {
this.doors = options.doors || 4;
this.state = options.state || "brand new";
}

drive() {}
breakDown() {}
}

class Truck {
constructor(options) {
this.state = options.state || "used";
this.wheelSize = options.wheelSize || "large";
}

drive() {}
breakDown() {}
}

const vehicleFactory = new AbstractVehicleFactory();

vehicleFactory.registerVehicle("car", Car);

By: Waleed Mousa


vehicleFactory.registerVehicle("truck", Truck);

const car = vehicleFactory.getVehicle("car", { color: "yellow", state: "like


new" });
const truck = vehicleFactory.getVehicle("truck", { wheelSize: "small", color:
"blue" });

console.log(car);
console.log(truck);

4. Builder Pattern

The Builder pattern allows a client to construct a complex object step


by step by specifying the type and content only. Construction details
are hidden from the client entirely.

class CarBuilder {
constructor() {
this.car = null;
}

step1() {
this.car = new Car();
}

step2() {
this.car.addParts();
}

get() {
return this.car;
}
}

class Car {
constructor() {
this.doors = 0;
}

addParts() {
this.doors = 4;
}

say() {

By: Waleed Mousa


console.log("I am a " + this.doors + "-door car");
}
}

const builder = new CarBuilder();


builder.step1();
builder.step2();
const car = builder.get();
car.say(); // I am a 4-door car

5. Prototype Pattern

The Prototype pattern is a creational pattern based on cloning a


pre-configured object. The idea is to use prototypical objects to point
out the different object kinds, new objects being created by copying
this prototype.

class Car {
constructor(model, color) {
this.model = model;
this.color = color;
}

clone() {
return new Car(this.model, this.color);
}
}

const prototypeCar = new Car('Tesla Model 3', 'Red');

const car1 = prototypeCar.clone();


const car2 = prototypeCar.clone();

console.log(car1.model); // Tesla Model 3


console.log(car2.color); // Red

These are the main types of creational patterns, each used in different
scenarios. Understanding the difference between them and when to use
which is important, and that comes with time and practice. In the next
part, we will go over Structural Design Patterns.

By: Waleed Mousa


Chapter 3: Structural Design Patterns

These patterns deal with object composition and ensure that different
parts of a system work together in a structured and efficient manner.

The main Structural Design Patterns we will cover are:

1. Adapter
2. Decorator
3. Facade
4. Composite
5. Proxy

1. Adapter Pattern

The Adapter Pattern helps two incompatible interfaces to work together.


This type of design pattern comes under structural pattern as this
pattern combines the capability of two independent interfaces.

Let's assume you've created an application that outputs logs to the


console, and now you want to make the app to write logs to a file. But
there's a problem: your logging module's log(message) method expects a
string, while the file system module's write(file, data) method expects
a file path and data.

Here's how an adapter could solve the problem:

class Logger {
log(message) {
console.log(message);
}
}

class Filesystem {
write(file, data) {
console.log(`Writing "${data}" to file ${file}`);
// Actual file writing code goes here
}
}

By: Waleed Mousa


class FilesystemAdapter {
constructor(fs) {
this.fs = fs;
}

log(message) {
const file = '/path/to/log/file';
this.fs.write(file, message);
}
}

const logger = new Logger();


logger.log('This is a log message');

const fs = new Filesystem();


const fsAdapter = new FilesystemAdapter(fs);
fsAdapter.log('This is another log message');

In this example, FilesystemAdapter is an adapter for the Filesystem


class. It wraps the Filesystem's write method in a new log method,
which allows it to be used just like the Logger class.

2. Decorator Pattern

The Decorator Pattern allows behavior to be added to an individual


object, either statically or dynamically, without affecting the
behavior of other objects from the same class. This type of design
pattern comes under structural pattern as this pattern acts as a
wrapper to existing class.

Imagine you're building a coffee order system, and you need to allow
customers to add optional extras like milk, sugar, and whip to their
orders.

By: Waleed Mousa


Here's how you could use a decorator to implement this:

class Coffee {
cost() {
return 1;
}
}

class Milk extends Coffee {


cost() {
return 1.5;
}
}

class Sugar extends Coffee {


cost() {
return 1.2;
}
}

class Whip extends Coffee {


cost() {
return 1.7;
}
}

const coffee = new Coffee();


console.log(coffee.cost()); // 1

const coffeeWithMilk = new Milk();


console.log(coffeeWithMilk.cost()); // 1.5

const coffeeWithSugar = new Sugar();


console.log(coffeeWithSugar.cost()); // 1.2

const coffeeWithWhip = new Whip();


console.log(coffeeWithWhip.cost()); // 1.7

Here, Milk, Sugar, and Whip are decorators for the Coffee class. Each
one adds its own behavior to the Coffee's cost method.

By: Waleed Mousa


3. Facade Pattern

The Facade Pattern provides a unified interface to a set of interfaces


in a subsystem. Facade defines a higher-level interface that makes the
subsystem easier to use.

Imagine you have a complex subsystem for handling customer orders, with
separate classes for order creation, inventory checking, payment
processing, and shipping. You could use a facade to simplify this:

class OrderSubsystem {
placeOrder() {
console.log('Order placed.');
}
}

class InventorySubsystem {
checkInventory() {
console.log('Inventory checked.');
}
}

class PaymentSubsystem {
processPayment() {
console.log('Payment processed.');
}
}

class ShippingSubsystem {
shipOrder() {
console.log('Order shipped.');
}
}

class OrderFacade {
constructor() {
this.orderSubsystem = new OrderSubsystem();
this.inventorySubsystem = new InventorySubsystem();
this.paymentSubsystem = new PaymentSubsystem();
this.shippingSubsystem = new ShippingSubsystem();
}

By: Waleed Mousa


placeOrder() {
this.orderSubsystem.placeOrder();
this.inventorySubsystem.checkInventory();
this.paymentSubsystem.processPayment();
this.shippingSubsystem.shipOrder();
}
}

const facade = new OrderFacade();


facade.placeOrder();

Here, OrderFacade is a facade for the order handling subsystem. It


simplifies the interface to the subsystem by providing a single
placeOrder method that hides the complexity of the underlying
subsystem.

4. Composite Pattern

The Composite Pattern composes objects into tree structures to


represent part-whole hierarchies. Composite lets clients treat
individual objects and compositions of objects uniformly.

Consider a file system, where a directory can contain files or other


directories. Here's how you could use a composite to represent this:

class File {
constructor(name) {
this.name = name;
}

display() {
console.log(this.name);
}
}

class Directory {
constructor(name) {
this.name = name;
this.children = [];
}

By: Waleed Mousa


add(child) {
this.children.push(child);
}

remove(child) {
const index = this.children.indexOf(child);
if (index > -1) {
this.children.splice(index, 1);
}
}

display(indentation = '') {
console.log(`${indentation}${this.name}`);
for (const child of this.children) {
child.display(`${indentation} `);
}
}
}

const root = new Directory('root');


const home = new Directory('home');
const conf = new File('conf');
const readme = new File('README.md');

root.add(home);
root.add(conf);
home.add(readme);

root.display();

In this example, File and Directory are composites. They both have a
display method, but Directory also has add and remove methods for
managing child nodes.

5. Proxy Pattern

The Proxy Pattern provides a surrogate or placeholder for another


object to control access to it. This type of design pattern comes under
structural pattern.

Consider a heavy database object that takes a lot of memory and


processing power to instantiate. If you need to perform simple actions

By: Waleed Mousa


like getting the status of the database, it would be wasteful to create
the entire object. Instead, you could use a proxy:

class HeavyDatabase {
constructor() {
console.log('Creating HeavyDatabase instance...');
// Lots of initialization code...
}

getStatus() {
return 'OK';
}
}

class DatabaseProxy {
getStatus() {
if (!this.database) {
this.database = new HeavyDatabase();
}
return this.database.getStatus();
}
}

const proxy = new DatabaseProxy();


console.log(proxy.getStatus()); // 'OK'

Here, DatabaseProxy is a proxy for the HeavyDatabase class. It has a


getStatus method that creates an instance of HeavyDatabase only when
it's really needed.

Remember, these are simplified examples and real-world use cases can be
more complex. The key to understanding design patterns is to grasp the
"why" behind using them. In the next part, we will discuss some
exercises to help you understand these patterns better.

By: Waleed Mousa


Chapter 4: Behavioral Design Patterns

These design patterns focus on communication between objects and how


they interact with each other.

1. Strategy Pattern

In the Strategy Pattern, you create objects which represent various


strategies and a context object whose behavior varies as per its
strategy object. The strategy object changes the executing algorithm of
the context object. This pattern falls under behavioral patterns.

Let's implement a simple payment system using the strategy pattern.

class PaymentStrategy {
static Card(user) {
return `User ${user} will pay using Credit Card`;
}

static Paypal(user) {
return `User ${user} will pay using PayPal`;
}

static Bitcoin(user) {
return `User ${user} will pay using Bitcoin`;
}
}

class Shopping {
constructor(strategy = 'Card') {
this.strategy = PaymentStrategy[strategy];
}

changeStrategy(newStrategy) {
this.strategy = PaymentStrategy[newStrategy];
}

checkout(user) {
return this.strategy(user);
}
}

By: Waleed Mousa


let shopping = new Shopping();
console.log(shopping.checkout('John')); // User John will pay using Credit
Card

shopping.changeStrategy('Paypal');
console.log(shopping.checkout('John')); // User John will pay using PayPal

In this example, PaymentStrategy represents different payment


strategies. Shopping can switch its payment method at runtime.

2. Observer Pattern

The Observer Pattern defines a one-to-many dependency between objects so


that when one object changes state, all its dependents are notified and
updated automatically. The object that watches on the state of another
object are called Observer and the object that is being watched is
called Subject.

Here's an example of a simple newsletter system:

class Newsletter {
constructor() {
this.subscribers = [];
}

subscribe(observer) {
this.subscribers.push(observer);
}

unsubscribe(observer) {
const index = this.subscribers.indexOf(observer);
if (index > -1) {
this.subscribers.splice(index, 1);
}
}

notify(data) {
for (let i = 0; i < this.subscribers.length; i++) {
this.subscribers[i](data);
}
}
}

By: Waleed Mousa


const newsletter = new Newsletter();

const john = data => console.log(`Message to John: ${data}`);


const jane = data => console.log(`Message to Jane: ${data}`);

newsletter.subscribe(john);
newsletter.subscribe(jane);

newsletter.notify('New edition out now!');


// Message to John: New edition out now!
// Message to Jane: New edition out now!

newsletter.unsubscribe(john);
newsletter.notify('Second edition out now!');
// Message to Jane: Second edition out now!

In this example, john and jane are observers of newsletter. When


newsletter calls notify, all of its observers are notified.

3. Command Pattern

The Command Pattern aims to encapsulate a request as an object, thus


allowing users to parameterize other objects with queues, requests, and
operations. Here's a simple implementation:

class Calculator {
constructor() {
this.value = 0;
}

execute(command) {
this.value = command.execute(this.value);
}

undo(command) {
this.value = command.undo(this.value);
}
}

class AddCommand {
constructor(valueToAdd) {
this.valueToAdd = valueToAdd;
}

By: Waleed Mousa


execute(currentValue) {
return currentValue + this.valueToAdd;
}

undo(currentValue) {
return currentValue - this.valueToAdd;
}
}

let calculator = new Calculator();


let addFiveCommand = new AddCommand(5);

calculator.execute(addFiveCommand);
console.log(calculator.value); // 5

calculator.undo(addFiveCommand);
console.log(calculator.value); // 0

In this example, Calculator is the invoker, and AddCommand is a


command. Calculator can execute or undo AddCommand (or any other
command that follows the same interface).

4. Iterator Pattern

The Iterator pattern is a design pattern in which an iterator is used


to traverse a container and access the container's elements. The
iterator pattern decouples algorithms from containers.

Let's implement a simple array iterator:

class ArrayIterator {
constructor(arr) {
this.array = arr;
this.index = 0;
}

hasNext() {
return this.index < this.array.length;
}

next() {
return this.array[this.index++];
}

By: Waleed Mousa


}

let arr = [1, 2, 3, 4, 5];


let iterator = new ArrayIterator(arr);

while(iterator.hasNext()) {
console.log(iterator.next());
}

In this example, ArrayIterator is an iterator for arrays. It can


iterate through the array from start to finish, providing one element at
a time.

5. State Pattern

In the State pattern, an object's behavior changes based on its state.


This type of design pattern comes under behavior pattern. In State
pattern, we create objects which represent various states and a context
object whose behavior varies as its state object changes.

Here's a simple implementation:

class TrafficLight {
constructor() {
this.states = [new GreenLight(), new YellowLight(), new RedLight()];
this.current = this.states[0];
}

change() {
const totalStates = this.states.length;
let currentIndex = this.states.findIndex(light => light ===
this.current);

if (currentIndex + 1 < totalStates) this.current =


this.states[currentIndex + 1];
else this.current = this.states[0];
}

sign() {
return this.current.message;
}
}

By: Waleed Mousa


class Light {
constructor(light, message) {
this.light = light;
this.message = message;
}
}

class GreenLight extends Light {


constructor() {
super('green', 'Go');
}
}

class YellowLight extends Light {


constructor() {
super('yellow', 'Careful');
}
}

class RedLight extends Light {


constructor() {
super('red', 'Stop');
}
}

const trafficLight = new TrafficLight();

console.log(trafficLight.sign()); // Go
trafficLight.change();
console.log(trafficLight.sign()); // Careful
trafficLight.change();
console.log(trafficLight.sign()); // Stop

In this example, TrafficLight is a context object whose behavior varies


as its state object changes.

6. Template Method Pattern

The Template Method pattern provides a method in a super class and


allows subclasses to override the method. The super class method is a
"template method" and contains a series of method calls that every
subclass object will call.

By: Waleed Mousa


Let's look at a simplified example:

class Builder {
build() {
this.test();
this.lint();
this.assemble();
this.deploy();
}

test() {
throw new Error('You have to implement the method test!');
}

lint() {
throw new Error('You have to implement the method lint!');
}

assemble() {
throw new Error('You have to implement the method assemble!');
}

deploy() {
throw new Error('You have to implement the method deploy!');
}
}

class JavaScriptBuilder extends Builder {


test() {
console.log('Running javascript tests');
}

lint() {
console.log('Linting the javascript code');
}

assemble() {
console.log('Assembling the javascript build');
}

deploy() {
console.log('Deploying javascript build to server');
}
}

By: Waleed Mousa


const javascriptBuilder = new JavaScriptBuilder();
javascriptBuilder.build();

// Output:
// Running javascript tests
// Linting the javascript code
// Assembling the javascript build
// Deploying javascript build to server

In this example, Builder is an abstract class that provides a template


method build(). JavaScriptBuilder provides the implementation for each
step in the build process.

7. Visitor Pattern

The Visitor pattern is a way of separating an algorithm from an object


structure on which it operates. A practical result of this separation
is the ability to add new operations to existing object structures
without modifying those structures. Here's a simple implementation:

class Monkey {
shout() {
console.log('Ooh oo aa aa!');
}

accept(operation) {
operation.visitMonkey(this);
}
}

class Lion {
roar() {
console.log('Roaaar!');
}

accept(operation) {
operation.visitLion(this);
}
}

class Dolphin {
speak() {
console.log('Tuut tuttu tuutt!');

By: Waleed Mousa


}

accept(operation) {
operation.visitDolphin(this);
}
}

class Speak {
visitMonkey(monkey) {
monkey.shout();
}

visitLion(lion) {
lion.roar();
}

visitDolphin(dolphin) {
dolphin.speak();
}
}

const monkey = new Monkey();


const lion = new Lion();
const dolphin = new Dolphin();
const speak = new Speak();

monkey.accept(speak); // Ooh oo aa aa!


lion.accept(speak); // Roaaar!
dolphin.accept(speak); // Tuut tuttu tuutt!

In this example, Monkey, Lion and Dolphin all accept an operation and
call the corresponding method of the operation on themselves.

Final Notes:

it's important to note that design patterns don't exist in isolation.


In real-world projects, multiple design patterns can be used together
to solve complex problems.

Let's talk about Pattern Relationships and Combinations.

By: Waleed Mousa


Patterns are not usually used in isolation but in combination with
other patterns. Here are some common pattern relationships:

1. Composite and Iterator


○ Composite and Iterator can work together. The Iterator
provides a way to access the components of a Composite
sequentially without exposing its underlying representation.
2. Facade and Singleton
○ A Facade can often be implemented as a Singleton since a
single Facade object is sufficient in most cases.
3. Factory Method and Strategy
○ Factory Methods are often used in Strategies. The Strategy
becomes the creator class and employs Factory Methods to
implement different variations of the algorithm.

To get a feel for how these patterns work together, consider this
scenario:

A RobotBuilder (Builder pattern) is used to create a Robot (Composite


pattern). The Robot consists of different parts like Head, Body, Arms,
Legs. Each part can be a composite of other smaller parts. For example,
Head could be a composite of Eyes, Ears, Mouth, and Nose.

The RobotBuilder can use a RobotPartsFactory (Abstract Factory pattern)


to create the different parts. The robot parts could be of different
types, like MetalHead, PlasticBody, RubberArms, WoodenLegs, and each
type could have different implementations of functions like move(),
function(), repair(), etc.

The RobotBuilder could also use a RobotAssemblyStrategy (Strategy


pattern) to determine the order and the way in which the parts are
assembled.

Finally, a RobotController (Facade pattern) could provide a simple


interface for clients to interact with the Robot, encapsulating the
complexity of the interactions between the different parts.

By: Waleed Mousa


As you can see, patterns often work together to create a coherent
architecture.

By: Waleed Mousa


API Quick Reference
HTTP Verbs
┣ GET : Retrieve data from the server

┣ POST : Send data to the server to create a resource

┣ PUT : Send data to the server to update a resource

┣ PATCH : Send data to the server to update a resource partially

┗ DELETE : Delete a resource from the server

┣ TRACE : Returns the full HTTP request received by the server for
debugging and diagnostic purposes

┣ OPTIONS : Returns the HTTP methods supported by the server for the
requested URL

┣ CONNECT : Converts the request connection to a transparent TCP/IP


tunnel for secure communication

┗ PURGE : Invalidates a cached resource

┣ LOCK : Locks the resource for exclusive use by the client

┣ UNLOCK : Unlocks the resource previously locked by the client

┣ MKCOL : Creates a new collection resource

┗ COPY : Copies the resource identified by the Request-URI to the


destination URI.

HTTP Status Codes


┣ 1xx : Informational

┣ 2xx : Success

┣ 3xx : Redirection

┣ 4xx : Client Errors

┗ 5xx : Server Errors

By: Waleed Mousa


API Quick Reference
Response Headers
┣ Content-Type : Specifies the MIME type of the data in the response
body

┣ Content-Length : Specifies the length of the response body in bytes

┣ Cache-Control : Specifies the caching behavior of the response

┣ Location : Specifies the URI of a resource that can be used to


retrieve the requested resource

┣ Server : Specifies the name and version of the server software that
generated the response

┣ Access-Control-Allow-Origin : Specifies which origins are allowed to


access the resource

┣ Set-Cookie : Specifies a cookie that should be stored by the client


and sent back to the server with future requests

┣ Expires : Specifies the date and time after which the response is
considered stale

┗ Last-Modified : Specifies the date and time the resource was last
modified.

API Design
┣ REST : Representational State Transfer, a design pattern for
building web services

┣ SOAP : Simple Object Access Protocol, a messaging protocol for


exchanging structured data

┣ GraphQL : A query language and runtime for building APIs

┗ API Gateway : A service that manages, protects, and scales APIs

By: Waleed Mousa


API Quick Reference
API Architectures
┣ SOA : Service-Oriented Architecture, an architectural style for
building distributed systems

┣ Microservices : An architectural style for building complex


applications as a suite of small, independent services

┣ Serverless : A cloud computing execution model where the cloud


provider manages the infrastructure and automatically allocates resources
as needed

┣ Event-Driven : An architectural style where the flow of data between


components is triggered by events

┗ RESTful API : An architectural style that uses HTTP requests to


GET, POST, PUT, and DELETE data.

API Design Patterns


┣ Adapter Pattern : A pattern that converts the interface of a class
into another interface that clients expect

┣ Decorator Pattern : A pattern that adds behavior to an individual


object dynamically

┣ Proxy Pattern : A pattern that provides a surrogate or placeholder


for another object to control access to it

┣ Chain of Responsibility Pattern : A pattern that delegates commands


to a chain of processing objects

┗ Observer Pattern : A pattern that defines a one-to-many dependency


between objects so that when one object changes state, all its dependents
are notified and updated automatically.

By: Waleed Mousa


API Quick Reference
API Security
┣ OAuth : An open standard for authorization used for protecting APIs

┣ JWT : JSON Web Tokens, a standard for securely transmitting


information between parties as a JSON object

┣ SSL/TLS : Secure Sockets Layer/Transport Layer Security, a protocol


for establishing a secure connection between a client and a server

┣ API Key : A secret token used to authenticate API requests

┗ Rate Limiting : A technique used to limit the number of requests


that can be made to an API over a specific period of time

┣ OpenID Connect : An authentication layer built on top of OAuth that


allows users to be authenticated across multiple domains

┣ Cross-Origin Resource Sharing (CORS) : A mechanism that allows many


resources (e.g., fonts, JavaScript, etc.) on a web page to be requested
from another domain outside the domain from which the resource originated

API Testing
┣ Postman : A popular tool for testing and debugging APIs

┣ SoapUI : A tool for testing SOAP and REST web services

┣ Swagger : A tool for designing, building, and testing APIs

┣ JMeter : A tool for testing the performance of APIs

┗ TestRail : A test management tool for planning, executing, and


tracking API tests

┣ Dredd : A command-line tool for testing API documentation against


its backend implementation

┣ REST Assured : A Java-based library for testing RESTful APIs

By: Waleed Mousa


API Quick Reference

┣ Karate DSL : A testing framework for API testing using Gherkin


syntax

┣ HttpMaster : A tool for testing and debugging APIs

┗ Assertible : A tool for testing and monitoring APIs with automated


tests.

API Development
┣ Node.js : A JavaScript runtime for building server-side
applications

┣ Express : A popular framework for building web applications and


APIs with Node.js

┣ Django : A Python web framework for building web applications and


APIs

┣ Flask : A lightweight Python web framework for building web


applications and APIs

┗ Spring : A Java framework for building enterprise-level web


applications and APIs

┣ Swagger Editor : A tool for designing and documenting APIs using


the OpenAPI specification

┣ Postman : A tool for testing and debugging APIs

┣ Insomnia : A tool for designing, testing, and debugging APIs

┣ Paw : A tool for designing and testing APIs on Mac OS

┗ API Blueprint : A high-level API description language for building


RESTful APIs.

By: Waleed Mousa


API Quick Reference
API Implementation Platforms
┣ Firebase : A mobile and web application development platform
developed by Google

┣ Backendless : A mobile and web application development platform


that allows developers to build and deploy applications without backend
coding

┣ Parse Server : An open-source version of the Parse backend that can


be deployed to any infrastructure

┣ Amazon API Gateway : A fully managed service that makes it easy for
developers to create, publish, maintain, monitor, and secure APIs

┗ Microsoft Azure API Management : A fully managed service that


enables users to publish, secure, transform, maintain, and monitor APIs.

API Performance
┣ Caching : A technique for improving API performance by storing
responses in a cache

┣ Throttling : A technique for limiting the rate of requests to an


API to prevent overload

┣ Load Balancing : A technique for distributing traffic evenly across


multiple servers to improve API performance

┣ Content Delivery Network (CDN) : A distributed system of servers


that delivers content to users based on their geographic location to
improve API performance

┗ Edge Computing : A computing paradigm that brings computation and


data storage closer to the location where it is needed to reduce latency
and improve API performance.

By: Waleed Mousa


API Quick Reference
API Monitoring
┣ Pingdom : A tool for monitoring the uptime and performance of APIs

┣ New Relic : A tool for monitoring the performance of APIs and other
web applications

┣ Datadog : A monitoring and analytics platform for cloud-scale


applications and APIs

┣ Sumo Logic : A cloud-based log management and analytics platform


for APIs and other applications

┗ Loggly : A cloud-based log management platform for monitoring APIs


and other applications

API Standards
┣ JSON API : A specification for building APIs that use JSON as the
data format

┣ HAL : Hypertext Application Language, a standard for building


hypermedia-driven APIs

┣ JSON-LD : A format for representing linked data on the web

┣ OData : Open Data Protocol, a standard for building and consuming


RESTful APIs

┗ AsyncAPI : A specification for building event-driven APIs.

By: Waleed Mousa


API Quick Reference
API Standards Organizations
┣ W3C : The World Wide Web Consortium, an international community
that develops web standards

┣ IETF : The Internet Engineering Task Force, an open standards


organization that develops and promotes Internet standards

┣ OASIS : Organization for the Advancement of Structured Information


Standards, a nonprofit consortium that drives the development,
convergence, and adoption of open standards for the global information
society

┣ RESTful API Modeling Language (RAML) : A YAML-based language for


describing RESTful APIs developed by MuleSoft

┗ JSON API : A specification for building APIs that use JSON as the
data format.

API Infrastructure
┣ Kubernetes : An open-source platform for managing containerized
workloads and services

┣ OpenShift : A container application platform that builds on top of


Kubernetes

┣ Docker Swarm : A native clustering and orchestration solution for


Docker

┣ Consul : A service mesh solution that provides service discovery,


configuration, and segmentation capabilities

┗ Istio : A service mesh solution that provides traffic management,


security, and observability capabilities.

By: Waleed Mousa


API Quick Reference
API Governance
┣ API Management : The process of creating, publishing, and
monitoring APIs in a secure and scalable way

┣ API Monetization : The process of generating revenue from APIs by


charging developers for usage

┣ API Versioning : The process of managing changes to APIs over time

┣ API Analytics : The process of collecting and analyzing data on API


usage and performance

┗ API Gateway : A service that manages, protects, and scales APIs.

API Documentation
┣ OpenAPI : A specification for building APIs in YAML or JSON format

┣ API Blueprint : A high-level API description language for building


RESTful APIs

┣ RAML : A YAML-based language for describing RESTful APIs

┣ Swagger UI : A tool for visualizing and interacting with APIs that


have been described using the OpenAPI specification

┗ Slate : A tool for generating beautiful, responsive API


documentation.

API Deployment
┣ Heroku : A cloud platform for deploying, managing, and scaling web
applications and APIs

┣ AWS Elastic Beanstalk : A service for deploying and scaling web


applications and APIs on AWS

By: Waleed Mousa


API Quick Reference

┣ Azure App Service : A service for deploying and scaling web


applications and APIs on Azure

┣ Google App Engine : A service for deploying and scaling web


applications and APIs on GCP

┗ Docker : A containerization platform used for packaging and


deploying applications

┣ AWS Lambda : A serverless compute service for running code in


response to events

┣ Azure Functions : A serverless compute service for running code in


response to events

┣ Google Cloud Functions : A serverless compute service for running


code in response to events

┣ Netlify : A cloud platform for deploying and managing static


websites and APIs

┗ Vercel : A cloud platform for deploying and managing static


websites and APIs

API Security
┣ OAuth : An open standard for authorization used by many social
media platforms and APIs

┣ OpenID Connect : An authentication layer built on top of OAuth that


allows users to be authenticated across multiple domains

┣ JSON Web Tokens (JWT) : A method for representing claims securely


between two parties

By: Waleed Mousa


API Quick Reference
┣ Cross-Origin Resource Sharing (CORS) : A mechanism that allows many
resources (e.g., fonts, JavaScript, etc.) on a web page to be requested
from another domain outside the domain from which the resource originated

┗ API Keys : A secret token that identifies an API client to the


server and allows the client to access resources.

API Best Practices


┣ Versioning : A technique for managing changes to APIs over time

┣ Pagination : A technique for breaking up large API responses into


smaller, more manageable chunks

┣ Caching : A technique for improving API performance by storing


responses in a cache

┣ Error Handling : A technique for returning meaningful error


messages to API clients

┗ HATEOAS : Hypermedia as the Engine of Application State, a


constraint of RESTful APIs that requires the API to provide links to
related resources

API Tutorials
┣ Getting Started with RESTful APIs by Tania Rascia

┣ API Design Best Practices by Martin Fowler

┣ Testing RESTful Web Services Made Easy Using the REST Assured
Framework by Dinesh Rajput

┣ API Gateway Concepts and Options by AWS

┣ Building Secure APIs by Auth0

┗ RESTful API Designing guidelines — The best practices by Mahesh


Haldar
By: Waleed Mousa
API Quick Reference
API Guides
┣ REST API Tutorial by Guru99

┣ A Beginner’s Guide to HTTP and REST by Linode

┣ REST API Design: Resource Modeling by Oracle

┣ API Security Best Practices by Google Cloud

┗ API Governance Handbook by WSO2.

API Tools
┣ API Studio : A web-based IDE for designing and testing APIs

┣ Stoplight : A collaborative platform for designing, documenting,


and testing APIs

┣ Apigee : A full lifecycle API management platform that allows


developers to design, secure, deploy, and analyze APIs

┣ Azure API Management : A fully managed service that enables users


to publish, secure, transform, maintain, and monitor APIs

┗ Postman Learning Center : A hub for learning how to use Postman to


design, develop, and test APIs.

By: Waleed Mousa


Git Ultimate Guide
Configuration
┣ Set user name: git config --global user.name "Your Name"

┣ Set email: git config --global user.email "youremail@example.com"

┣ Check settings: git config --list

┣ Set default editor: git config --global core.editor nano

┣ Set merge tool: git config --global merge.tool vimdiff

┗ Set diff tool: git config --global diff.tool meld

Repository Setup
┣ Create new repository: git init

┣ Clone a repository: git clone <repo-url>

┣ Add remote repository: git remote add <name> <repo-url>

┗ List remote repositories: git remote -v

Basic Workflow
┣ Check status: git status

┣ Add changes to staging area: git add <file>

┣ Commit changes: git commit -m "Commit message"

┣ Push changes: git push

┣ Pull changes: git pull

┗ Fetch changes: git fetch

By: Waleed Mousa


Git Ultimate Guide
Branching
┣ Create new branch: git branch <branch-name>

┣ Switch to branch: git checkout <branch-name>

┣ Create and switch to branch: git checkout -b <branch-name>

┣ List branches: git branch

┣ Merge branch into current branch: git merge <branch-name>

┣ Delete branch: git branch -d <branch-name>

┗ Force delete branch: git branch -D <branch-name>

Stashing
┣ Save changes to stash: git stash save "Message"

┣ List stashes: git stash list

┣ Apply latest stash: git stash apply

┣ Apply specific stash: git stash apply stash@{2}

┣ Delete latest stash: git stash drop

┗ Delete specific stash: git stash drop stash@{2}

Tagging
┣ Create annotated tag: git tag -a <tag-name> -m "Message"

┣ Create lightweight tag: git tag <tag-name>

┣ List tags: git tag

┣ Push tag to remote: git push --tags

┗ Delete tag: git tag -d <tag-name>

By: Waleed Mousa


Git Ultimate Guide
Undoing Changes
┣ Discard changes in working directory: git checkout -- <file>

┣ Unstage file: git reset HEAD <file>

┣ Undo commit: git reset HEAD~

┣ Discard all changes since last commit: git reset --hard HEAD

┗ Revert commit: git revert <commit-hash>

Miscellaneous
┣ Show commit history: git log

┣ Show differences between files: git diff <file>

┣ Show differences between commits: git diff <commit1> <commit2>

┣ Create a new commit from selected changes: git cherry-pick <commit-hash>

┣ Configure global ignore file: git config --global core.excludesfile


~/.gitignore_global

┗ Show file history: git log -- <file>

Git Flow
┣ Initialize Git Flow: git flow init

┣ Start new feature: git flow feature start <feature-name>

┣ Finish feature: git flow feature finish <feature-name>

┣ Start new release: git flow release start <version>

┣ Finish release: git flow release finish <version>

┣ Start new hotfix: git flow hotfix start <version>

┣ Finish hotfix: git flow hotfix finish <version>

┗ Publish branch: git flow publish <branch>

By: Waleed Mousa


Git Ultimate Guide
Git Aliases
┣ Create alias: git config --global alias.<alias-name> "<command>"

┣ List aliases: git config --global --get-regexp alias

┗ Delete alias: git config --global --unset alias.<alias-name>

Git Hooks
┣ Client-side hooks: .git/hooks/

┣ Server-side hooks: hooks/

┗ Sample hook: pre-commit

Advanced Workflow
┣ Rebase: git rebase <branch>

┣ Squash commits: git rebase -i HEAD~<number-of-commits>

┣ Interactive rebase: git rebase -i <commit-hash>

┣ Cherry-pick commit range: git cherry-pick <start-commit>..<end-commit>

┣ Merge with merge commit: git merge --no-ff <branch>

┣ Merge with fast-forward: git merge --ff-only <branch>

┣ Resolve merge conflicts: git mergetool

┣ Bisect: git bisect <start> <end>

┗ Reset branch to previous commit: git reset <commit>

By: Waleed Mousa


Git Ultimate Guide
Git Internals
┣ Object model: blobs, trees, commits, tags

┣ Git directory structure: objects, refs, HEAD

┣ Object hash: git hash-object <file>

┣ Create Git object: echo <content> | git hash-object -w --stdin

┣ Git index: staging area and cache

┣ Packfiles: compressed object storage

┣ Git object storage optimization: git gc

┣ Git hooks internals: pre-commit, post-commit, pre-push

┗ Git objects with multiple parents: merge commits

Git Graphical Clients


┣ GitHub Desktop

┣ SourceTree

┣ GitKraken

┣ Git Extensions

┗ TortoiseGit

By: Waleed Mousa


Git Ultimate Guide
Tips & Tricks
┣ Interactive staging: git add -p

┣ Amend commit message: git commit --amend

┣ Show commit changes: git show <commit-hash>

┣ Show changes from branch to another: git diff <branch1>..<branch2>

┣ Show local branches tracking remote branches: git branch -vv

┣ Show repository statistics: git diff --stat

┗ Show who last modified each line of a file: git blame <file>

Best Practices
┣ Keep commits small and focused

┣ Write meaningful commit messages

┣ Use feature branches for development

┣ Keep the master branch stable

┣ Pull before pushing

┣ Use Git Flow for complex workflows

┣ Use Git Hooks for automation

┗ Use Git Aliases for frequently used commands

By: Waleed Mousa


Git Ultimate Guide

Resources
┣ Official Git website: https://git-scm.com/

┣ Git documentation: https://git-scm.com/doc

┣ Pro Git book: https://git-scm.com/book/en/v2

┣ GitHub Learning Lab: https://lab.github.com/

┣ Atlassian Git tutorial: https://www.atlassian.com/git/tutorials

┣ Git Immersion: https://gitimmersion.com/

┣ Git Cheat Sheet by GitHub: https://education.github.com/git-cheat-sheet-


education.pdf

┗ Git Cheat Sheet by Tower: https://www.git-tower.com/blog/git-cheat-sheet/

By: Waleed Mousa

You might also like