Sunday, 10 August 2025

List Comprehensions in Python

Suppose we need to create a list with first 10 multiple of 6 in it, So we may do this with a normal

for loop or with list comprehensions, Let's see both of them and understand the difference.

Normal For loop

list1 =[]

for n in range(1,11):

list1.append(n*6)

print(list1)

Output:

[6, 12, 18, 24, 30, 36, 42, 48, 54, 60]

List comprehension

list1 = [n*6 for n in range(1,11)]

print(list1)

Output:

[6, 12, 18, 24, 30, 36, 42, 48, 54, 60]

We got the same output using list comprehensions just by writing a line of code.

In general list comprehension

[<the_expression> for <the_element> in <the_iterable>]

Comparing this with our example n*6 is the expression, n is the element, range(1,11) is the

iterable.

Applying list comprehension with a condition

Now, Suppose we need to create a list of multiple of 6 for just even numbers between 1 to 10.

list1 =[]

for n in range(1,11):

if n%2==0:

list1.append(n*6)

print(list1)

Output:

[12, 24, 36, 48, 60]

Using list comprehensions

list1 = [n*6 for n in range(1,11) if n%2==0]

print(list1)

Output:

[12, 24, 36, 48, 60]

In general list comprehension

[<the_expression> for <the_element> in <the_iterable> if <the_condition>]

Comparing this with our example n*6 is the expression, n is the element, range(1,11) is the

iterable and n%2==0 is the condition.

Applying list comprehension with if-else condition

Now, Suppose we need to create a list of multiple of 6 for even numbers between 1 to 10 and

multiple of 5 for rest of the numbers.

list1 =[]

for n in range(1,11):

if n%2==0:

list1.append(n*6)

else:

list1.append(n*5)

print(list1)

Output:

[5, 12, 15, 24, 25, 36, 35, 48, 45, 60]

Using list comprehensions

list1 = [n*6 if n%2==0 else n*5 for n in range(1,11)]

print(list1)

Output:

[5, 12, 15, 24, 25, 36, 35, 48, 45, 60]

In general list comprehension

[<the_expression> if <the_condition> else <other_expression> for <the_element> in

<the_iterable>]

Comparing this with our example n*6 is the expression, n%2==0 is the condition, n*5 is the

other expression, n is the element and range(1,11) is the iterable.

Applying list comprehension with Nested loops

Now, Suppose we need to multiply n ranging from 1 to 10 with first 1 then 2 and then 3.

list1 =[]

for i in range(1,4):

for j in range(1,11):

list1.append(i*j)

print(list1)

Output:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30]

list1 = [i*j for i in range(1,4) for j in range(1,11) ]

print(list1)

Output:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30]

In general list comprehension

[ <the_expression> for <element_a> in <iterable_a> (optional if <condition_a>)

for <element_b> in <iterable_b> (optional if <condition_b>)

for <element_c> in <iterable_c> (optional if <condition_c>)

... and so on ...]

Comparing this with our example i*j is the expression, i is the element_a, j is the element_b,

range(1,4) is the iterable_a and range(1,11) is the iterable_b.

Monday, 4 August 2025

🏃‍♂️ My First 5K Run: Lessons Beyond the Track 🏅

 This weekend, I completed my first 5K run—and it turned out to be much more than just a race. It was a powerful reminder of how mindset, consistency, and perspective shape our journey in life and leadership. Here are some takeaways that I believe apply far beyond running:


🔹 Mindset Matters

Waking up at 4 AM and showing up at the venue wasn’t easy—but it all started with setting the right mindset. Whether in personal goals or professional ambitions, your mental readiness sets the tone.


🔹 Consistency Pays Off

Until now, I had never run 5K in one stretch. But consistent preparation and showing up every day made it possible. Small, steady steps lead to big breakthroughs.


🔹 Hurdles Are Inevitable

There were moments I wanted to stop. Fatigue, breathlessness, and doubts crept in. But determination and focus helped me push through. Obstacles are a given—your response defines your path.


🔹 Run Your Own Race

In the race, some were ahead, others behind. But that didn’t make anyone better or worse. Everyone has a different pace and strength. The real win is in honoring your unique journey—comparison is a distraction.


🔹 Be a Leader Who Lifts Others

Some runners took time to cheer and guide others. It reminded me that leadership isn’t just about reaching your own goals—it’s also about helping others cross their finish lines.


🔹 Embrace Highs and Lows

The uphill was tough, the downhill easy. Just like life. Stay grounded in good times, and stay hopeful in the hard ones. Both are temporary. What matters is how you carry yourself through them.


🔹 No Excuses

The most inspiring moment? A 97-year-old man completing the race. If he can do it, we can too. Excuses are often just stories we tell ourselves. Let’s choose action instead.


🔹 Winning Is Great. Learning Is Greater.

I didn’t run to win—I ran to grow. And even if you don’t cross first, every experience carries insights to fuel your next step.

Saturday, 17 May 2025

SOLID Pattern Quick Notes - Design Patterns for Writing Classes in OOPS

SOLID is a famous design pattern for writing classes in OOPS

S: Single Responsibility Principle

O: Open Closed Principle

L: Liskov's Substitution Principle

I: Interface Segregation Principle

D: Dependency Inversion Principle


Single Responsibility Principle (SRP)

- Class should have one and only one responsibility

- Write a Class to achieve one goal

- SRP helps to provide high maintainability and better visibility to control across application module

Open Closed Principle (OCP)

- Software components should be open for extension, but closed for modification.

- Our classes should not contain constraints that will require other developers to modify our classes in order to accomplish their job – only by extend our classes to accomplish their job.

- It helps to achieve  software extensibility in a versatile, intuitive, and non-harmful way.

 Liskov's Substitution Principle (LSP)

 -Derived types must be completely substitutable for their base types.

- Objects of subclasses must behave in the same way as the objects of super classes.

- This principle useful for runtime-type identification followed by the cast.

Interface Segregation Principle (ISP)

-Clients should not be forced to implement unnecessary methods that they will not use.  

-splits an interface into two or more interfaces until clients are not forced to implement methods that they will not use.

This principle stands for Clients should not be forced to implement unnecessary methods that they will not use. In other words, we should split an interface into two or more interfaces until clients are not forced to implement methods that they will not use. For example, consider the Connection interface, which has three methods: connect(), socket(), and http(). A client may want to implement this interface only for connections via HTTP. Therefore, they don't need the socket() method. Most of the time, the client will leave this method empty, and this is a bad design. In order to avoid such situations, simply split the Connection interface into two interfaces; SocketConnection with the socket() method, and HttpConnection with the http() method. Both interfaces will extend the Connection interface that remains with the common method, connect()


Dependency Inversion Principle (DIP).

-Depend on abstractions, not on concretions.

- sustains the use of abstract layers to bind concrete modules together instead of having concrete modules that depend on other concrete modules.

- sustains the decoupling of concrete modules.

This principle stands for Depend on abstractions, not on concretions. This means that we should rely on abstract layers to bind concrete modules together instead of having concrete modules that depend on other concrete modules. To accomplish this, all concrete modules should expose abstractions only. This way, the concrete modules allow extension of the functionality or plug-in in another concrete module while retaining the decoupling of concrete modules. Commonly, high coupling occurs between high-level concrete modules and low-level concrete modules.

Ex: A database JDBC URL, PostgreSQLJdbcUrl, can be a low-level module, while a class that connects to the database may represent a high-level module, such as ConnectToDatabase#connect().

Time Complexity of an Algorithm

 

Time Complexity Description Example
O(1) Constant time     Accessing an element in an array
O(log n) Logarithmic time     Binary search
O(n) Linear time     Iterating over an array
O(n log n) Linearithmic time     Merge sort, quicksort (avg case)
O(n²) Quadratic time     Nested loops over array
O(2ⁿ) Exponential time     Recursive Fibonacci
O(n!) Factorial time     Permutations / Travelling Salesman


O(1) - Constant Time - The time doesn't change no matter how big the input is

Ex: Accessing element of an array . int x = nums[0];  // Always takes the same time

O(log n) - Logarithimic Time : You reduce the input size by half each step i.e Guessing a number between 1 and 100 by halving the range each time

Ex: Binary Search 

 while (low <= high) {
    int mid = (low + high) / 2;
    if (nums[mid] == target) return mid;
    else if (nums[mid] < target) low = mid + 1;
    else high = mid - 1;
}

O(n) - Linear time - Time grows directly with input size

Ex : Looping over an array i.e. Reading every page in a book once
for (int i : nums) {
    System.out.println(i);
}

O(n log n) - Linearithmic Time - A combination of linear and logarithimic Time - common in efficient sorting

Ex; Merge Sort, quick sort (average case) - merge sort splits the array ( log n) and merges each part (O (n))

Sorting a phone book - split , sort and merge

O(n2) - Quadratic Time - Time grows with the square of the input - often from the nested loops
Ex; Two loops over an array - i.e. comparing every student with every other student

for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
        // O(n²)
    }
}

O(2n) - Exponential Time - Each step doubles the number of operations

Ex : Recursive fibonacci 

int fib(int n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);
}

O(n!) - Factorial Time - Insanely slow for even modest input sizes — involves all permutations.

Ex : Generating all permutations - Trying every possible seating arrangement for n people.

void permute(List<Integer> nums, int index) {
    if (index == nums.size()) return;
    for (int i = index; i < nums.size(); i++) {
        Collections.swap(nums, i, index);
        permute(nums, index + 1);
        Collections.swap(nums, i, index);
    }
}








Monday, 5 May 2025

System Design Preparation

 Following are the system design preparation materials

https://github.com/ashishps1/awesome-system-design-resources?tab=readme-ov-file

https://dev.to/somadevtoo/9-software-architecture-patterns-for-distributed-systems-2o86

https://martinfowler.com/articles/patterns-of-distributed-systems/