diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index a0bd05f47ec8..edee3bc4febb 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -3,6 +3,5 @@ ARG VARIANT=3.13-bookworm
 FROM mcr.microsoft.com/vscode/devcontainers/python:${VARIANT}
 COPY requirements.txt /tmp/pip-tmp/
 RUN python3 -m pip install --upgrade pip \
-  && python3 -m pip install --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
-  && pipx install pre-commit ruff \
-  && pre-commit install
+  && python3 -m pip install --no-cache-dir -r /tmp/pip-tmp/requirements.txt \
+  && pipx install pre-commit ruff
diff --git a/.devcontainer/README.md b/.devcontainer/README.md
index ec3cdb61de7a..8056578ad3a8 100644
--- a/.devcontainer/README.md
+++ b/.devcontainer/README.md
@@ -1 +1,42 @@
-https://code.visualstudio.com/docs/devcontainers/tutorial
+# Development Container
+
+This is **Devcontainer** configuration to provide a consistent development environment for all contributors.
+
+## Features
+
+- [x] Pre-configured **Python environment**
+- [x] Automatic installation of **pre-commit hooks**
+- [x] **Ruff** linter ready to check your code
+- [x] **Oh My Zsh** with plugins:
+- `zsh-autosuggestions`
+- `zsh-syntax-highlighting`
+
+## Usage
+
+1. Install [**Docker** ](https://www.docker.com/get-started/) and [**Visual Studio Code**](https://code.visualstudio.com/)
+2. Install the **Remote - Containers** extension in VS Code
+
+    - Do `CTRL+P`, paste this command and press `Enter`
+
+        ```shell
+        ext install ms-vscode-remote.remote-containers
+        ```
+3. Open this repository in VS Code
+4. When prompted, click **"Reopen in Container"**
+5. Wait for the environment to build and initialize
+
+After setup:
+
+- `pre-commit` hooks are installed
+- `ruff` and other tools are available
+- The shell uses Zsh by default
+
+## Tips
+
+To manually run checks on all files:
+
+```bash
+pre-commit run --all-files
+```
+
+> For further information here's [Microsoft tutorial about devcontainers.](https://code.visualstudio.com/docs/devcontainers/tutorial)
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index e23263f5b9de..4951d5eb268d 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -7,10 +7,12 @@
 			// Update 'VARIANT' to pick a Python version: 3, 3.11, 3.10, 3.9, 3.8
 			// Append -bullseye or -buster to pin to an OS version.
 			// Use -bullseye variants on local on arm64/Apple Silicon.
-			"VARIANT": "3.13-bookworm",
+			"VARIANT": "3.13-bookworm"
 		}
 	},
 
+	"postCreateCommand": "zsh .devcontainer/post_install",
+
 	// Configure tool-specific properties.
 	"customizations": {
 		// Configure properties specific to VS Code.
@@ -20,7 +22,8 @@
 				"python.defaultInterpreterPath": "/usr/local/bin/python",
 				"python.linting.enabled": true,
 				"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
-				"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy"
+				"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
+				"terminal.integrated.defaultProfile.linux": "zsh"
 			},
 
 			// Add the IDs of extensions you want installed when the container is created.
diff --git a/.devcontainer/post_install b/.devcontainer/post_install
new file mode 100755
index 000000000000..589ee361f5cb
--- /dev/null
+++ b/.devcontainer/post_install
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+echo "Begin post-installation steps..."
+
+set -e
+
+echo "Installing pre-commit hooks..."
+pre-commit install
+
+echo "Installing Oh My Zsh plugins..."
+
+# Install zsh-autosuggestions if not present
+if [ ! -d "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autosuggestions" ]; then
+  echo "Cloning zsh-autosuggestions..."
+  git clone https://github.com/zsh-users/zsh-autosuggestions \
+    "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autosuggestions"
+fi
+
+# Install zsh-syntax-highlighting if not present
+if [ ! -d "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting" ]; then
+  echo "Cloning zsh-syntax-highlighting..."
+  git clone https://github.com/zsh-users/zsh-syntax-highlighting.git \
+    "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting"
+fi
+
+echo "Configuring plugins in ~/.zshrc..."
+sed -i '/^plugins=/c\plugins=(git zsh-autosuggestions zsh-syntax-highlighting)' ~/.zshrc
+
+echo "Post-installation steps completed successfully. Enjoy!"
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 8b83cb41c79a..69192db0c4c6 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -9,14 +9,14 @@ jobs:
   build:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - uses: astral-sh/setup-uv@v6
         with:
           enable-cache: true
           cache-dependency-glob: uv.lock
       - uses: actions/setup-python@v5
         with:
-          python-version: 3.13
+          python-version: 3.x
           allow-prereleases: true
       - run: uv sync --group=test
       - name: Run tests
@@ -30,6 +30,7 @@ jobs:
           --ignore=project_euler/
           --ignore=quantum/q_fourier_transform.py
           --ignore=scripts/validate_solutions.py
+          --ignore=web_programming/current_stock_price.py
           --ignore=web_programming/fetch_anime_and_play.py
           --cov-report=term-missing:skip-covered
           --cov=. .
diff --git a/.github/workflows/devcontainer_ci.yml b/.github/workflows/devcontainer_ci.yml
new file mode 100644
index 000000000000..71623e5e6e69
--- /dev/null
+++ b/.github/workflows/devcontainer_ci.yml
@@ -0,0 +1,19 @@
+name: Test DevContainer Build
+
+on:
+  push:
+    paths:
+      - ".devcontainer/**"
+  pull_request:
+    paths:
+      - ".devcontainer/**"
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v5
+      - uses: devcontainers/ci@v0.3
+        with:
+          push: never
+          runCmd: "true"
diff --git a/.github/workflows/directory_writer.yml b/.github/workflows/directory_writer.yml
index 55d89f455a25..f5167f8d1a58 100644
--- a/.github/workflows/directory_writer.yml
+++ b/.github/workflows/directory_writer.yml
@@ -3,10 +3,10 @@
 name: directory_writer
 on: [push]
 jobs:
-  build:
+  directory_writer:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
         with:
           fetch-depth: 0
       - uses: actions/setup-python@v5
diff --git a/.github/workflows/project_euler.yml b/.github/workflows/project_euler.yml
index eaf4150e4eaa..8b8cb2a1e68f 100644
--- a/.github/workflows/project_euler.yml
+++ b/.github/workflows/project_euler.yml
@@ -14,7 +14,7 @@ jobs:
   project-euler:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - uses: astral-sh/setup-uv@v6
       - uses: actions/setup-python@v5
         with:
@@ -24,7 +24,7 @@ jobs:
   validate-solutions:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - uses: astral-sh/setup-uv@v6
       - uses: actions/setup-python@v5
         with:
diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml
index ec9f0202bd7e..7bcc2850782f 100644
--- a/.github/workflows/ruff.yml
+++ b/.github/workflows/ruff.yml
@@ -11,6 +11,6 @@ jobs:
   ruff:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - uses: astral-sh/setup-uv@v6
       - run: uvx ruff check --output-format=github .
diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml
index 2010041d80c5..a5502a586297 100644
--- a/.github/workflows/sphinx.yml
+++ b/.github/workflows/sphinx.yml
@@ -25,7 +25,7 @@ jobs:
   build_docs:
     runs-on: ubuntu-24.04-arm
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - uses: astral-sh/setup-uv@v6
       - uses: actions/setup-python@v5
         with:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 288e3f591403..252cfebc53a8 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v5.0.0
+    rev: v6.0.0
     hooks:
       - id: check-executables-have-shebangs
       - id: check-toml
@@ -16,9 +16,9 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.11.9
+    rev: v0.12.9
     hooks:
-      - id: ruff
+      - id: ruff-check
       - id: ruff-format
 
   - repo: https://github.com/codespell-project/codespell
@@ -29,7 +29,7 @@ repos:
           - tomli
 
   - repo: https://github.com/tox-dev/pyproject-fmt
-    rev: "v2.5.1"
+    rev: v2.6.0
     hooks:
       - id: pyproject-fmt
 
@@ -53,12 +53,11 @@ repos:
         args:
           - --explicit-package-bases
           - --ignore-missing-imports
-          - --install-types # See mirrors-mypy README.md
+          - --install-types
           - --non-interactive
-        additional_dependencies: [types-requests]
 
   - repo: https://github.com/pre-commit/mirrors-prettier
-    rev: "v4.0.0-alpha.8"
+    rev: v4.0.0-alpha.8
     hooks:
       - id: prettier
         types_or: [toml, yaml]
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 4129f9c1a5e2..81d6f4c70864 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -443,6 +443,7 @@
   * [Present Value](financial/present_value.py)
   * [Price Plus Tax](financial/price_plus_tax.py)
   * [Simple Moving Average](financial/simple_moving_average.py)
+  * [Straight Line Depreciation](financial/straight_line_depreciation.py)
   * [Time And Half Pay](financial/time_and_half_pay.py)
 
 ## Fractals
@@ -480,6 +481,7 @@
   * [Bi Directional Dijkstra](graphs/bi_directional_dijkstra.py)
   * [Bidirectional A Star](graphs/bidirectional_a_star.py)
   * [Bidirectional Breadth First Search](graphs/bidirectional_breadth_first_search.py)
+  * [Bidirectional Search](graphs/bidirectional_search.py)
   * [Boruvka](graphs/boruvka.py)
   * [Breadth First Search](graphs/breadth_first_search.py)
   * [Breadth First Search 2](graphs/breadth_first_search_2.py)
@@ -789,6 +791,7 @@
   * [Sumset](maths/sumset.py)
   * [Sylvester Sequence](maths/sylvester_sequence.py)
   * [Tanh](maths/tanh.py)
+  * [Test Factorial](maths/test_factorial.py)
   * [Test Prime Check](maths/test_prime_check.py)
   * [Three Sum](maths/three_sum.py)
   * [Trapezoidal Rule](maths/trapezoidal_rule.py)
diff --git a/backtracking/sum_of_subsets.py b/backtracking/sum_of_subsets.py
index f34d3ca34339..f26f179f8725 100644
--- a/backtracking/sum_of_subsets.py
+++ b/backtracking/sum_of_subsets.py
@@ -1,5 +1,5 @@
 """
-The sum-of-subsetsproblem states that a set of non-negative integers, and a
+The sum-of-subsets problem states that a set of non-negative integers, and a
 value M, determine all possible subsets of the given set whose summation sum
 equal to given M.
 
@@ -7,10 +7,20 @@
 can be used only once.
 """
 
-from __future__ import annotations
 
+def generate_sum_of_subsets_solutions(nums: list[int], max_sum: int) -> list[list[int]]:
+    """
+    The main function. For list of numbers 'nums' find the subsets with sum
+    equal to 'max_sum'
+
+    >>> generate_sum_of_subsets_solutions(nums=[3, 34, 4, 12, 5, 2], max_sum=9)
+    [[3, 4, 2], [4, 5]]
+    >>> generate_sum_of_subsets_solutions(nums=[3, 34, 4, 12, 5, 2], max_sum=3)
+    [[3]]
+    >>> generate_sum_of_subsets_solutions(nums=[3, 34, 4, 12, 5, 2], max_sum=1)
+    []
+    """
 
-def generate_sum_of_subsets_soln(nums: list[int], max_sum: int) -> list[list[int]]:
     result: list[list[int]] = []
     path: list[int] = []
     num_index = 0
@@ -34,7 +44,21 @@ def create_state_space_tree(
     This algorithm follows depth-fist-search and backtracks when the node is not
     branchable.
 
+    >>> path = []
+    >>> result = []
+    >>> create_state_space_tree(
+    ...     nums=[1],
+    ...     max_sum=1,
+    ...     num_index=0,
+    ...     path=path,
+    ...     result=result,
+    ...     remaining_nums_sum=1)
+    >>> path
+    []
+    >>> result
+    [[1]]
     """
+
     if sum(path) > max_sum or (remaining_nums_sum + sum(path)) < max_sum:
         return
     if sum(path) == max_sum:
@@ -51,16 +75,7 @@ def create_state_space_tree(
         )
 
 
-"""
-remove the comment to take an input from the user
-
-print("Enter the elements")
-nums = list(map(int, input().split()))
-print("Enter max_sum sum")
-max_sum = int(input())
+if __name__ == "__main__":
+    import doctest
 
-"""
-nums = [3, 34, 4, 12, 5, 2]
-max_sum = 9
-result = generate_sum_of_subsets_soln(nums, max_sum)
-print(*result)
+    doctest.testmod()
diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py
index 33b2529f017b..c690d29bd113 100644
--- a/ciphers/hill_cipher.py
+++ b/ciphers/hill_cipher.py
@@ -78,8 +78,10 @@ def replace_digits(self, num: int) -> str:
         'T'
         >>> hill_cipher.replace_digits(26)
         '0'
+        >>> hill_cipher.replace_digits(26.1)
+        '0'
         """
-        return self.key_string[round(num)]
+        return self.key_string[int(num)]
 
     def check_determinant(self) -> None:
         """
diff --git a/data_structures/binary_tree/non_recursive_segment_tree.py b/data_structures/binary_tree/non_recursive_segment_tree.py
index ca0d5c111c4f..7d1c965fab50 100644
--- a/data_structures/binary_tree/non_recursive_segment_tree.py
+++ b/data_structures/binary_tree/non_recursive_segment_tree.py
@@ -39,12 +39,12 @@
 from __future__ import annotations
 
 from collections.abc import Callable
-from typing import Any, Generic, TypeVar
+from typing import Any, TypeVar
 
 T = TypeVar("T")
 
 
-class SegmentTree(Generic[T]):
+class SegmentTree[T]:
     def __init__(self, arr: list[T], fnc: Callable[[T, T], T]) -> None:
         """
         Segment Tree constructor, it works just with commutative combiner.
diff --git a/data_structures/hashing/hash_map.py b/data_structures/hashing/hash_map.py
index 9213d6930f67..0d99e578b73e 100644
--- a/data_structures/hashing/hash_map.py
+++ b/data_structures/hashing/hash_map.py
@@ -10,14 +10,14 @@
 
 from collections.abc import Iterator, MutableMapping
 from dataclasses import dataclass
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 KEY = TypeVar("KEY")
 VAL = TypeVar("VAL")
 
 
-@dataclass(frozen=True, slots=True)
-class _Item(Generic[KEY, VAL]):
+@dataclass(slots=True)
+class _Item[KEY, VAL]:
     key: KEY
     val: VAL
 
@@ -72,16 +72,17 @@ def _try_set(self, ind: int, key: KEY, val: VAL) -> bool:
 
         If bucket is empty or key is the same, does insert and return True.
 
-        If bucket has another key or deleted placeholder,
-        that means that we need to check next bucket.
+        If bucket has another key that means that we need to check next bucket.
         """
         stored = self._buckets[ind]
         if not stored:
+            # A falsy item means that bucket was never used (None)
+            # or was deleted (_deleted).
             self._buckets[ind] = _Item(key, val)
             self._len += 1
             return True
         elif stored.key == key:
-            self._buckets[ind] = _Item(key, val)
+            stored.val = val
             return True
         else:
             return False
@@ -228,6 +229,27 @@ def __delitem__(self, key: KEY) -> None:
         Traceback (most recent call last):
         ...
         KeyError: 4
+
+        # Test resize down when sparse
+        ## Setup: resize up
+        >>> hm = HashMap(initial_block_size=100, capacity_factor=0.75)
+        >>> len(hm._buckets)
+        100
+        >>> for i in range(75):
+        ...     hm[i] = i
+        >>> len(hm._buckets)
+        100
+        >>> hm[75] = 75
+        >>> len(hm._buckets)
+        200
+
+        ## Resize down
+        >>> del hm[75]
+        >>> len(hm._buckets)
+        200
+        >>> del hm[74]
+        >>> len(hm._buckets)
+        100
         """
         for ind in self._iterate_buckets(key):
             item = self._buckets[ind]
diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py
index 7b15e69f13ca..41ef0ddd1005 100644
--- a/data_structures/heap/heap.py
+++ b/data_structures/heap/heap.py
@@ -2,7 +2,7 @@
 
 from abc import abstractmethod
 from collections.abc import Iterable
-from typing import Generic, Protocol, TypeVar
+from typing import Protocol, TypeVar
 
 
 class Comparable(Protocol):
@@ -22,7 +22,7 @@ def __eq__(self: T, other: object) -> bool:
 T = TypeVar("T", bound=Comparable)
 
 
-class Heap(Generic[T]):
+class Heap[T: Comparable]:
     """A Max Heap Implementation
 
     >>> unsorted = [103, 9, 1, 7, 11, 15, 25, 201, 209, 107, 5]
diff --git a/data_structures/heap/randomized_heap.py b/data_structures/heap/randomized_heap.py
index 12888c1f4089..9668d3ac2a45 100644
--- a/data_structures/heap/randomized_heap.py
+++ b/data_structures/heap/randomized_heap.py
@@ -4,12 +4,12 @@
 
 import random
 from collections.abc import Iterable
-from typing import Any, Generic, TypeVar
+from typing import Any, TypeVar
 
 T = TypeVar("T", bound=bool)
 
 
-class RandomizedHeapNode(Generic[T]):
+class RandomizedHeapNode[T: bool]:
     """
     One node of the randomized heap. Contains the value and references to
     two children.
@@ -73,7 +73,7 @@ def merge(
         return root1
 
 
-class RandomizedHeap(Generic[T]):
+class RandomizedHeap[T: bool]:
     """
     A data structure that allows inserting a new value and to pop the smallest
     values. Both operations take O(logN) time where N is the size of the
diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py
index 0839db711cb1..4e82569444fa 100644
--- a/data_structures/heap/skew_heap.py
+++ b/data_structures/heap/skew_heap.py
@@ -3,12 +3,12 @@
 from __future__ import annotations
 
 from collections.abc import Iterable, Iterator
-from typing import Any, Generic, TypeVar
+from typing import Any, TypeVar
 
 T = TypeVar("T", bound=bool)
 
 
-class SkewNode(Generic[T]):
+class SkewNode[T: bool]:
     """
     One node of the skew heap. Contains the value and references to
     two children.
@@ -87,7 +87,7 @@ def merge(
         return result
 
 
-class SkewHeap(Generic[T]):
+class SkewHeap[T: bool]:
     """
     A data structure that allows inserting a new value and to pop the smallest
     values. Both operations take O(logN) time where N is the size of the
diff --git a/data_structures/linked_list/skip_list.py b/data_structures/linked_list/skip_list.py
index 13e9a94a8698..f21ca70bbc82 100644
--- a/data_structures/linked_list/skip_list.py
+++ b/data_structures/linked_list/skip_list.py
@@ -7,13 +7,13 @@
 
 from itertools import pairwise
 from random import random
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 KT = TypeVar("KT")
 VT = TypeVar("VT")
 
 
-class Node(Generic[KT, VT]):
+class Node[KT, VT]:
     def __init__(self, key: KT | str = "root", value: VT | None = None):
         self.key = key
         self.value = value
@@ -49,7 +49,7 @@ def level(self) -> int:
         return len(self.forward)
 
 
-class SkipList(Generic[KT, VT]):
+class SkipList[KT, VT]:
     def __init__(self, p: float = 0.5, max_level: int = 16):
         self.head: Node[KT, VT] = Node[KT, VT]()
         self.level = 0
diff --git a/data_structures/queues/queue_by_list.py b/data_structures/queues/queue_by_list.py
index 4b05be9fd08e..182cc4147c47 100644
--- a/data_structures/queues/queue_by_list.py
+++ b/data_structures/queues/queue_by_list.py
@@ -1,13 +1,10 @@
 """Queue represented by a Python list"""
 
 from collections.abc import Iterable
-from typing import Generic, TypeVar
 
-_T = TypeVar("_T")
 
-
-class QueueByList(Generic[_T]):
-    def __init__(self, iterable: Iterable[_T] | None = None) -> None:
+class QueueByList[T]:
+    def __init__(self, iterable: Iterable[T] | None = None) -> None:
         """
         >>> QueueByList()
         Queue(())
@@ -16,7 +13,7 @@ def __init__(self, iterable: Iterable[_T] | None = None) -> None:
         >>> QueueByList((i**2 for i in range(1, 4)))
         Queue((1, 4, 9))
         """
-        self.entries: list[_T] = list(iterable or [])
+        self.entries: list[T] = list(iterable or [])
 
     def __len__(self) -> int:
         """
@@ -58,7 +55,7 @@ def __repr__(self) -> str:
 
         return f"Queue({tuple(self.entries)})"
 
-    def put(self, item: _T) -> None:
+    def put(self, item: T) -> None:
         """Put `item` to the Queue
 
         >>> queue = QueueByList()
@@ -72,7 +69,7 @@ def put(self, item: _T) -> None:
 
         self.entries.append(item)
 
-    def get(self) -> _T:
+    def get(self) -> T:
         """
         Get `item` from the Queue
 
@@ -118,7 +115,7 @@ def rotate(self, rotation: int) -> None:
         for _ in range(rotation):
             put(get(0))
 
-    def get_front(self) -> _T:
+    def get_front(self) -> T:
         """Get the front item from the Queue
 
         >>> queue = QueueByList((10, 20, 30))
diff --git a/data_structures/queues/queue_by_two_stacks.py b/data_structures/queues/queue_by_two_stacks.py
index cd62f155a63b..f9e302ffcedd 100644
--- a/data_structures/queues/queue_by_two_stacks.py
+++ b/data_structures/queues/queue_by_two_stacks.py
@@ -1,13 +1,10 @@
 """Queue implementation using two stacks"""
 
 from collections.abc import Iterable
-from typing import Generic, TypeVar
 
-_T = TypeVar("_T")
 
-
-class QueueByTwoStacks(Generic[_T]):
-    def __init__(self, iterable: Iterable[_T] | None = None) -> None:
+class QueueByTwoStacks[T]:
+    def __init__(self, iterable: Iterable[T] | None = None) -> None:
         """
         >>> QueueByTwoStacks()
         Queue(())
@@ -16,8 +13,8 @@ def __init__(self, iterable: Iterable[_T] | None = None) -> None:
         >>> QueueByTwoStacks((i**2 for i in range(1, 4)))
         Queue((1, 4, 9))
         """
-        self._stack1: list[_T] = list(iterable or [])
-        self._stack2: list[_T] = []
+        self._stack1: list[T] = list(iterable or [])
+        self._stack2: list[T] = []
 
     def __len__(self) -> int:
         """
@@ -59,7 +56,7 @@ def __repr__(self) -> str:
         """
         return f"Queue({tuple(self._stack2[::-1] + self._stack1)})"
 
-    def put(self, item: _T) -> None:
+    def put(self, item: T) -> None:
         """
         Put `item` into the Queue
 
@@ -74,7 +71,7 @@ def put(self, item: _T) -> None:
 
         self._stack1.append(item)
 
-    def get(self) -> _T:
+    def get(self) -> T:
         """
         Get `item` from the Queue
 
diff --git a/data_structures/stacks/stack.py b/data_structures/stacks/stack.py
index 93698f5aa116..3ffa32d4167f 100644
--- a/data_structures/stacks/stack.py
+++ b/data_structures/stacks/stack.py
@@ -1,6 +1,6 @@
 from __future__ import annotations
 
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 T = TypeVar("T")
 
@@ -13,7 +13,7 @@ class StackUnderflowError(BaseException):
     pass
 
 
-class Stack(Generic[T]):
+class Stack[T]:
     """A stack is an abstract data type that serves as a collection of
     elements with two principal operations: push() and pop(). push() adds an
     element to the top of the stack, and pop() removes an element from the top
diff --git a/data_structures/stacks/stack_with_doubly_linked_list.py b/data_structures/stacks/stack_with_doubly_linked_list.py
index 50c5236e073c..ad01cd7eb6aa 100644
--- a/data_structures/stacks/stack_with_doubly_linked_list.py
+++ b/data_structures/stacks/stack_with_doubly_linked_list.py
@@ -3,19 +3,19 @@
 
 from __future__ import annotations
 
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 T = TypeVar("T")
 
 
-class Node(Generic[T]):
+class Node[T]:
     def __init__(self, data: T):
         self.data = data  # Assign data
         self.next: Node[T] | None = None  # Initialize next as null
         self.prev: Node[T] | None = None  # Initialize prev as null
 
 
-class Stack(Generic[T]):
+class Stack[T]:
     """
     >>> stack = Stack()
     >>> stack.is_empty()
diff --git a/data_structures/stacks/stack_with_singly_linked_list.py b/data_structures/stacks/stack_with_singly_linked_list.py
index 8e77c2b967ef..57a68679eee1 100644
--- a/data_structures/stacks/stack_with_singly_linked_list.py
+++ b/data_structures/stacks/stack_with_singly_linked_list.py
@@ -3,12 +3,12 @@
 from __future__ import annotations
 
 from collections.abc import Iterator
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 T = TypeVar("T")
 
 
-class Node(Generic[T]):
+class Node[T]:
     def __init__(self, data: T):
         self.data = data
         self.next: Node[T] | None = None
@@ -17,7 +17,7 @@ def __str__(self) -> str:
         return f"{self.data}"
 
 
-class LinkedStack(Generic[T]):
+class LinkedStack[T]:
     """
     Linked List Stack implementing push (to top),
     pop (from top) and is_empty
diff --git a/dynamic_programming/matrix_chain_order.py b/dynamic_programming/matrix_chain_order.py
index d612aea7b99d..6df43e84be28 100644
--- a/dynamic_programming/matrix_chain_order.py
+++ b/dynamic_programming/matrix_chain_order.py
@@ -5,13 +5,19 @@
 Implementation of Matrix Chain Multiplication
 Time Complexity: O(n^3)
 Space Complexity: O(n^2)
+
+Reference: https://en.wikipedia.org/wiki/Matrix_chain_multiplication
 """
 
 
-def matrix_chain_order(array):
+def matrix_chain_order(array: list[int]) -> tuple[list[list[int]], list[list[int]]]:
+    """
+    >>> matrix_chain_order([10, 30, 5])
+    ([[0, 0, 0], [0, 0, 1500], [0, 0, 0]], [[0, 0, 0], [0, 0, 1], [0, 0, 0]])
+    """
     n = len(array)
-    matrix = [[0 for x in range(n)] for x in range(n)]
-    sol = [[0 for x in range(n)] for x in range(n)]
+    matrix = [[0 for _ in range(n)] for _ in range(n)]
+    sol = [[0 for _ in range(n)] for _ in range(n)]
 
     for chain_length in range(2, n):
         for a in range(1, n - chain_length + 1):
@@ -28,26 +34,33 @@ def matrix_chain_order(array):
     return matrix, sol
 
 
-# Print order of matrix with Ai as Matrix
-def print_optiomal_solution(optimal_solution, i, j):
+def print_optimal_solution(optimal_solution: list[list[int]], i: int, j: int):
+    """
+    Print order of matrix with Ai as Matrix.
+    """
+
     if i == j:
         print("A" + str(i), end=" ")
     else:
         print("(", end=" ")
-        print_optiomal_solution(optimal_solution, i, optimal_solution[i][j])
-        print_optiomal_solution(optimal_solution, optimal_solution[i][j] + 1, j)
+        print_optimal_solution(optimal_solution, i, optimal_solution[i][j])
+        print_optimal_solution(optimal_solution, optimal_solution[i][j] + 1, j)
         print(")", end=" ")
 
 
 def main():
+    """
+    Size of matrix created from array [30, 35, 15, 5, 10, 20, 25] will be:
+    30*35 35*15 15*5 5*10 10*20 20*25
+    """
+
     array = [30, 35, 15, 5, 10, 20, 25]
     n = len(array)
-    # Size of matrix created from above array will be
-    # 30*35 35*15 15*5 5*10 10*20 20*25
+
     matrix, optimal_solution = matrix_chain_order(array)
 
     print("No. of Operation required: " + str(matrix[1][n - 1]))
-    print_optiomal_solution(optimal_solution, 1, n - 1)
+    print_optimal_solution(optimal_solution, 1, n - 1)
 
 
 if __name__ == "__main__":
diff --git a/financial/straight_line_depreciation.py b/financial/straight_line_depreciation.py
new file mode 100644
index 000000000000..e11e1c1364ce
--- /dev/null
+++ b/financial/straight_line_depreciation.py
@@ -0,0 +1,103 @@
+"""
+In accounting, depreciation refers to the decreases in the value
+of a fixed asset during the asset's useful life.
+When an organization purchases a fixed asset,
+the purchase expenditure is not recognized as an expense immediately.
+Instead, the decreases in the asset's value are recognized as expenses
+over the years during which the asset is used.
+
+The following methods are widely used
+for depreciation calculation in accounting:
+- Straight-line method
+- Diminishing balance method
+- Units-of-production method
+
+The straight-line method is the simplest and most widely used.
+This method calculates depreciation by spreading the cost evenly
+over the asset's useful life.
+
+The following formula shows how to calculate the yearly depreciation expense:
+
+- annual depreciation expense =
+    (purchase cost of asset - residual value) / useful life of asset(years)
+
+Further information on:
+https://en.wikipedia.org/wiki/Depreciation
+
+The function, straight_line_depreciation, returns a list of
+the depreciation expenses over the given period.
+"""
+
+
+def straight_line_depreciation(
+    useful_years: int,
+    purchase_value: float,
+    residual_value: float = 0.0,
+) -> list[float]:
+    """
+    Calculate the depreciation expenses over the given period
+    :param useful_years: Number of years the asset will be used
+    :param purchase_value: Purchase expenditure for the asset
+    :param residual_value: Residual value of the asset at the end of its useful life
+    :return: A list of annual depreciation expenses over the asset's useful life
+    >>> straight_line_depreciation(10, 1100.0, 100.0)
+    [100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0]
+    >>> straight_line_depreciation(6, 1250.0, 50.0)
+    [200.0, 200.0, 200.0, 200.0, 200.0, 200.0]
+    >>> straight_line_depreciation(4, 1001.0)
+    [250.25, 250.25, 250.25, 250.25]
+    >>> straight_line_depreciation(11, 380.0, 50.0)
+    [30.0, 30.0, 30.0, 30.0, 30.0, 30.0, 30.0, 30.0, 30.0, 30.0, 30.0]
+    >>> straight_line_depreciation(1, 4985, 100)
+    [4885.0]
+    """
+
+    if not isinstance(useful_years, int):
+        raise TypeError("Useful years must be an integer")
+
+    if useful_years < 1:
+        raise ValueError("Useful years cannot be less than 1")
+
+    if not isinstance(purchase_value, (float, int)):
+        raise TypeError("Purchase value must be numeric")
+
+    if not isinstance(residual_value, (float, int)):
+        raise TypeError("Residual value must be numeric")
+
+    if purchase_value < 0.0:
+        raise ValueError("Purchase value cannot be less than zero")
+
+    if purchase_value < residual_value:
+        raise ValueError("Purchase value cannot be less than residual value")
+
+    # Calculate annual depreciation expense
+    depreciable_cost = purchase_value - residual_value
+    annual_depreciation_expense = depreciable_cost / useful_years
+
+    # List of annual depreciation expenses
+    list_of_depreciation_expenses = []
+    accumulated_depreciation_expense = 0.0
+    for period in range(useful_years):
+        if period != useful_years - 1:
+            accumulated_depreciation_expense += annual_depreciation_expense
+            list_of_depreciation_expenses.append(annual_depreciation_expense)
+        else:
+            depreciation_expense_in_end_year = (
+                depreciable_cost - accumulated_depreciation_expense
+            )
+            list_of_depreciation_expenses.append(depreciation_expense_in_end_year)
+
+    return list_of_depreciation_expenses
+
+
+if __name__ == "__main__":
+    user_input_useful_years = int(input("Please Enter Useful Years:\n > "))
+    user_input_purchase_value = float(input("Please Enter Purchase Value:\n > "))
+    user_input_residual_value = float(input("Please Enter Residual Value:\n > "))
+    print(
+        straight_line_depreciation(
+            user_input_useful_years,
+            user_input_purchase_value,
+            user_input_residual_value,
+        )
+    )
diff --git a/graphs/bidirectional_search.py b/graphs/bidirectional_search.py
new file mode 100644
index 000000000000..b3ff9f75fd44
--- /dev/null
+++ b/graphs/bidirectional_search.py
@@ -0,0 +1,201 @@
+"""
+Bidirectional Search Algorithm.
+
+This algorithm searches from both the source and target nodes simultaneously,
+meeting somewhere in the middle. This approach can significantly reduce the
+search space compared to a traditional one-directional search.
+
+Time Complexity: O(b^(d/2)) where b is the branching factor and d is the depth
+Space Complexity: O(b^(d/2))
+
+https://en.wikipedia.org/wiki/Bidirectional_search
+"""
+
+from collections import deque
+
+
+def expand_search(
+    graph: dict[int, list[int]],
+    queue: deque[int],
+    parents: dict[int, int | None],
+    opposite_direction_parents: dict[int, int | None],
+) -> int | None:
+    if not queue:
+        return None
+
+    current = queue.popleft()
+    for neighbor in graph[current]:
+        if neighbor in parents:
+            continue
+
+        parents[neighbor] = current
+        queue.append(neighbor)
+
+        # Check if this creates an intersection
+        if neighbor in opposite_direction_parents:
+            return neighbor
+
+    return None
+
+
+def construct_path(current: int | None, parents: dict[int, int | None]) -> list[int]:
+    path: list[int] = []
+    while current is not None:
+        path.append(current)
+        current = parents[current]
+    return path
+
+
+def bidirectional_search(
+    graph: dict[int, list[int]], start: int, goal: int
+) -> list[int] | None:
+    """
+    Perform bidirectional search on a graph to find the shortest path.
+
+    Args:
+        graph: A dictionary where keys are nodes and values are lists of adjacent nodes
+        start: The starting node
+        goal: The target node
+
+    Returns:
+        A list representing the path from start to goal, or None if no path exists
+
+    Examples:
+        >>> graph = {
+        ...     0: [1, 2],
+        ...     1: [0, 3, 4],
+        ...     2: [0, 5, 6],
+        ...     3: [1, 7],
+        ...     4: [1, 8],
+        ...     5: [2, 9],
+        ...     6: [2, 10],
+        ...     7: [3, 11],
+        ...     8: [4, 11],
+        ...     9: [5, 11],
+        ...     10: [6, 11],
+        ...     11: [7, 8, 9, 10],
+        ... }
+        >>> bidirectional_search(graph=graph, start=0, goal=11)
+        [0, 1, 3, 7, 11]
+        >>> bidirectional_search(graph=graph, start=5, goal=5)
+        [5]
+        >>> disconnected_graph = {
+        ...     0: [1, 2],
+        ...     1: [0],
+        ...     2: [0],
+        ...     3: [4],
+        ...     4: [3],
+        ... }
+        >>> bidirectional_search(graph=disconnected_graph, start=0, goal=3) is None
+        True
+    """
+    if start == goal:
+        return [start]
+
+    # Check if start and goal are in the graph
+    if start not in graph or goal not in graph:
+        return None
+
+    # Initialize forward and backward search dictionaries
+    # Each maps a node to its parent in the search
+    forward_parents: dict[int, int | None] = {start: None}
+    backward_parents: dict[int, int | None] = {goal: None}
+
+    # Initialize forward and backward search queues
+    forward_queue = deque([start])
+    backward_queue = deque([goal])
+
+    # Intersection node (where the two searches meet)
+    intersection = None
+
+    # Continue until both queues are empty or an intersection is found
+    while forward_queue and backward_queue and intersection is None:
+        # Expand forward search
+        intersection = expand_search(
+            graph=graph,
+            queue=forward_queue,
+            parents=forward_parents,
+            opposite_direction_parents=backward_parents,
+        )
+
+        # If no intersection found, expand backward search
+        if intersection is not None:
+            break
+
+        intersection = expand_search(
+            graph=graph,
+            queue=backward_queue,
+            parents=backward_parents,
+            opposite_direction_parents=forward_parents,
+        )
+
+    # If no intersection found, there's no path
+    if intersection is None:
+        return None
+
+    # Construct path from start to intersection
+    forward_path: list[int] = construct_path(
+        current=intersection, parents=forward_parents
+    )
+    forward_path.reverse()
+
+    # Construct path from intersection to goal
+    backward_path: list[int] = construct_path(
+        current=backward_parents[intersection], parents=backward_parents
+    )
+
+    # Return the complete path
+    return forward_path + backward_path
+
+
+def main() -> None:
+    """
+    Run example of bidirectional search algorithm.
+
+    Examples:
+        >>> main()  # doctest: +NORMALIZE_WHITESPACE
+        Path from 0 to 11: [0, 1, 3, 7, 11]
+        Path from 5 to 5: [5]
+        Path from 0 to 3: None
+    """
+    # Example graph represented as an adjacency list
+    example_graph = {
+        0: [1, 2],
+        1: [0, 3, 4],
+        2: [0, 5, 6],
+        3: [1, 7],
+        4: [1, 8],
+        5: [2, 9],
+        6: [2, 10],
+        7: [3, 11],
+        8: [4, 11],
+        9: [5, 11],
+        10: [6, 11],
+        11: [7, 8, 9, 10],
+    }
+
+    # Test case 1: Path exists
+    start, goal = 0, 11
+    path = bidirectional_search(graph=example_graph, start=start, goal=goal)
+    print(f"Path from {start} to {goal}: {path}")
+
+    # Test case 2: Start and goal are the same
+    start, goal = 5, 5
+    path = bidirectional_search(graph=example_graph, start=start, goal=goal)
+    print(f"Path from {start} to {goal}: {path}")
+
+    # Test case 3: No path exists (disconnected graph)
+    disconnected_graph = {
+        0: [1, 2],
+        1: [0],
+        2: [0],
+        3: [4],
+        4: [3],
+    }
+    start, goal = 0, 3
+    path = bidirectional_search(graph=disconnected_graph, start=start, goal=goal)
+    print(f"Path from {start} to {goal}: {path}")
+
+
+if __name__ == "__main__":
+    main()
diff --git a/graphs/graph_adjacency_list.py b/graphs/graph_adjacency_list.py
index abc75311cd60..244e59e0e1bf 100644
--- a/graphs/graph_adjacency_list.py
+++ b/graphs/graph_adjacency_list.py
@@ -21,14 +21,14 @@
 import random
 import unittest
 from pprint import pformat
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 import pytest
 
 T = TypeVar("T")
 
 
-class GraphAdjacencyList(Generic[T]):
+class GraphAdjacencyList[T]:
     def __init__(
         self, vertices: list[T], edges: list[list[T]], directed: bool = True
     ) -> None:
diff --git a/graphs/graph_adjacency_matrix.py b/graphs/graph_adjacency_matrix.py
index 568c84166e4b..8eeeae786513 100644
--- a/graphs/graph_adjacency_matrix.py
+++ b/graphs/graph_adjacency_matrix.py
@@ -21,14 +21,14 @@
 import random
 import unittest
 from pprint import pformat
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 import pytest
 
 T = TypeVar("T")
 
 
-class GraphAdjacencyMatrix(Generic[T]):
+class GraphAdjacencyMatrix[T]:
     def __init__(
         self, vertices: list[T], edges: list[list[T]], directed: bool = True
     ) -> None:
diff --git a/graphs/graph_list.py b/graphs/graph_list.py
index 6563cbb76132..6b63590bf55a 100644
--- a/graphs/graph_list.py
+++ b/graphs/graph_list.py
@@ -6,12 +6,12 @@
 from __future__ import annotations
 
 from pprint import pformat
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 T = TypeVar("T")
 
 
-class GraphAdjacencyList(Generic[T]):
+class GraphAdjacencyList[T]:
     """
     Adjacency List type Graph Data Structure that accounts for directed and undirected
     Graphs.  Initialize graph object indicating whether it's directed or undirected.
diff --git a/graphs/minimum_spanning_tree_kruskal2.py b/graphs/minimum_spanning_tree_kruskal2.py
index 0ddb43ce8e6e..1f6d7255683b 100644
--- a/graphs/minimum_spanning_tree_kruskal2.py
+++ b/graphs/minimum_spanning_tree_kruskal2.py
@@ -1,11 +1,11 @@
 from __future__ import annotations
 
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 T = TypeVar("T")
 
 
-class DisjointSetTreeNode(Generic[T]):
+class DisjointSetTreeNode[T]:
     # Disjoint Set Node to store the parent and rank
     def __init__(self, data: T) -> None:
         self.data = data
@@ -13,7 +13,7 @@ def __init__(self, data: T) -> None:
         self.rank = 0
 
 
-class DisjointSetTree(Generic[T]):
+class DisjointSetTree[T]:
     # Disjoint Set DataStructure
     def __init__(self) -> None:
         # map from node name to the node object
@@ -46,7 +46,7 @@ def union(self, data1: T, data2: T) -> None:
         self.link(self.find_set(data1), self.find_set(data2))
 
 
-class GraphUndirectedWeighted(Generic[T]):
+class GraphUndirectedWeighted[T]:
     def __init__(self) -> None:
         # connections: map from the node to the neighbouring nodes (with weights)
         self.connections: dict[T, dict[T, int]] = {}
diff --git a/graphs/minimum_spanning_tree_prims2.py b/graphs/minimum_spanning_tree_prims2.py
index 6870cc80f844..d22111289a48 100644
--- a/graphs/minimum_spanning_tree_prims2.py
+++ b/graphs/minimum_spanning_tree_prims2.py
@@ -10,7 +10,7 @@
 from __future__ import annotations
 
 from sys import maxsize
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 T = TypeVar("T")
 
@@ -47,7 +47,7 @@ def get_child_right_position(position: int) -> int:
     return (2 * position) + 2
 
 
-class MinPriorityQueue(Generic[T]):
+class MinPriorityQueue[T]:
     """
     Minimum Priority Queue Class
 
@@ -184,7 +184,7 @@ def _swap_nodes(self, node1_pos: int, node2_pos: int) -> None:
         self.position_map[node2_elem] = node1_pos
 
 
-class GraphUndirectedWeighted(Generic[T]):
+class GraphUndirectedWeighted[T]:
     """
     Graph Undirected Weighted Class
 
@@ -217,7 +217,7 @@ def add_edge(self, node1: T, node2: T, weight: int) -> None:
         self.connections[node2][node1] = weight
 
 
-def prims_algo(
+def prims_algo[T](
     graph: GraphUndirectedWeighted[T],
 ) -> tuple[dict[T, int], dict[T, T | None]]:
     """
diff --git a/maths/radix2_fft.py b/maths/radix2_fft.py
index d41dc82d5588..ccd5cdcc0160 100644
--- a/maths/radix2_fft.py
+++ b/maths/radix2_fft.py
@@ -40,13 +40,13 @@ class FFT:
 
     Print product
     >>> x.product  # 2x + 3x^2 + 8x^3 + 4x^4 + 6x^5
-    [(-0+0j), (2+0j), (3+0j), (8+0j), (6+0j), (8+0j)]
+    [(-0-0j), (2+0j), (3-0j), (8-0j), (6+0j), (8+0j)]
 
     __str__ test
     >>> print(x)
     A = 0*x^0 + 1*x^1 + 2*x^0 + 3*x^2
     B = 0*x^2 + 1*x^3 + 2*x^4
-    A*B = 0*x^(-0+0j) + 1*x^(2+0j) + 2*x^(3+0j) + 3*x^(8+0j) + 4*x^(6+0j) + 5*x^(8+0j)
+    A*B = 0*x^(-0-0j) + 1*x^(2+0j) + 2*x^(3-0j) + 3*x^(8-0j) + 4*x^(6+0j) + 5*x^(8+0j)
     """
 
     def __init__(self, poly_a=None, poly_b=None):
@@ -147,7 +147,9 @@ def __multiply(self):
             inverce_c = new_inverse_c
             next_ncol *= 2
         # Unpack
-        inverce_c = [round(x[0].real, 8) + round(x[0].imag, 8) * 1j for x in inverce_c]
+        inverce_c = [
+            complex(round(x[0].real, 8), round(x[0].imag, 8)) for x in inverce_c
+        ]
 
         # Remove leading 0's
         while inverce_c[-1] == 0:
diff --git a/maths/test_factorial.py b/maths/test_factorial.py
new file mode 100644
index 000000000000..d80d88add745
--- /dev/null
+++ b/maths/test_factorial.py
@@ -0,0 +1,37 @@
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "pytest",
+# ]
+# ///
+
+import pytest
+
+from maths.factorial import factorial, factorial_recursive
+
+
+@pytest.mark.parametrize("function", [factorial, factorial_recursive])
+def test_zero(function):
+    assert function(0) == 1
+
+
+@pytest.mark.parametrize("function", [factorial, factorial_recursive])
+def test_positive_integers(function):
+    assert function(1) == 1
+    assert function(5) == 120
+    assert function(7) == 5040
+
+
+@pytest.mark.parametrize("function", [factorial, factorial_recursive])
+def test_large_number(function):
+    assert function(10) == 3628800
+
+
+@pytest.mark.parametrize("function", [factorial, factorial_recursive])
+def test_negative_number(function):
+    with pytest.raises(ValueError):
+        function(-3)
+
+
+if __name__ == "__main__":
+    pytest.main(["-v", __file__])
diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py
index a5940a38e836..dee9247282f9 100644
--- a/matrix/matrix_class.py
+++ b/matrix/matrix_class.py
@@ -260,7 +260,7 @@ def add_row(self, row: list[int], position: int | None = None) -> None:
         if position is None:
             self.rows.append(row)
         else:
-            self.rows = self.rows[0:position] + [row] + self.rows[position:]
+            self.rows = [*self.rows[0:position], row, *self.rows[position:]]
 
     def add_column(self, column: list[int], position: int | None = None) -> None:
         type_error = TypeError(
@@ -279,7 +279,7 @@ def add_column(self, column: list[int], position: int | None = None) -> None:
             self.rows = [self.rows[i] + [column[i]] for i in range(self.num_rows)]
         else:
             self.rows = [
-                self.rows[i][0:position] + [column[i]] + self.rows[i][position:]
+                [*self.rows[i][0:position], column[i], *self.rows[i][position:]]
                 for i in range(self.num_rows)
             ]
 
diff --git a/other/least_recently_used.py b/other/least_recently_used.py
index cb692bb1b1c0..d96960868488 100644
--- a/other/least_recently_used.py
+++ b/other/least_recently_used.py
@@ -2,12 +2,12 @@
 
 import sys
 from collections import deque
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 T = TypeVar("T")
 
 
-class LRUCache(Generic[T]):
+class LRUCache[T]:
     """
     Page Replacement Algorithm, Least Recently Used (LRU) Caching.
 
diff --git a/other/lfu_cache.py b/other/lfu_cache.py
index 5a143c739b9d..6eaacff2966a 100644
--- a/other/lfu_cache.py
+++ b/other/lfu_cache.py
@@ -1,13 +1,13 @@
 from __future__ import annotations
 
 from collections.abc import Callable
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 T = TypeVar("T")
 U = TypeVar("U")
 
 
-class DoubleLinkedListNode(Generic[T, U]):
+class DoubleLinkedListNode[T, U]:
     """
     Double Linked List Node built specifically for LFU Cache
 
@@ -30,7 +30,7 @@ def __repr__(self) -> str:
         )
 
 
-class DoubleLinkedList(Generic[T, U]):
+class DoubleLinkedList[T, U]:
     """
     Double Linked List built specifically for LFU Cache
 
@@ -161,7 +161,7 @@ def remove(
         return node
 
 
-class LFUCache(Generic[T, U]):
+class LFUCache[T, U]:
     """
     LFU Cache to store a given capacity of data. Can be used as a stand-alone object
     or as a function decorator.
diff --git a/other/lru_cache.py b/other/lru_cache.py
index 4f0c843c86cc..058b03b021bc 100644
--- a/other/lru_cache.py
+++ b/other/lru_cache.py
@@ -1,13 +1,13 @@
 from __future__ import annotations
 
 from collections.abc import Callable
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 T = TypeVar("T")
 U = TypeVar("U")
 
 
-class DoubleLinkedListNode(Generic[T, U]):
+class DoubleLinkedListNode[T, U]:
     """
     Double Linked List Node built specifically for LRU Cache
 
@@ -28,7 +28,7 @@ def __repr__(self) -> str:
         )
 
 
-class DoubleLinkedList(Generic[T, U]):
+class DoubleLinkedList[T, U]:
     """
     Double Linked List built specifically for LRU Cache
 
@@ -143,7 +143,7 @@ def remove(
         return node
 
 
-class LRUCache(Generic[T, U]):
+class LRUCache[T, U]:
     """
     LRU Cache to store a given capacity of data. Can be used as a stand-alone object
     or as a function decorator.
diff --git a/pyproject.toml b/pyproject.toml
index 2ead5cd51ae8..b680cc0d439e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -112,11 +112,14 @@ lint.ignore = [
   "EXE001",  # Shebang is present but file is not executable -- DO NOT FIX
   "G004",    # Logging statement uses f-string
   "ISC001",  # Conflicts with ruff format -- DO NOT FIX
+  "PLC0415", # import-outside-top-level -- DO NOT FIX
   "PLC1901", # `{}` can be simplified to `{}` as an empty string is falsey
   "PLW060",  # Using global for `{name}` but no assignment is done -- DO NOT FIX
+  "PLW1641", # eq-without-hash
   "PLW2901", # PLW2901: Redefined loop variable -- FIX ME
   "PT011",   # `pytest.raises(Exception)` is too broad, set the `match` parameter or use a more specific exception
   "PT018",   # Assertion should be broken down into multiple parts
+  "PT028",   # pytest-parameter-with-default-argument
   "S101",    # Use of `assert` detected -- DO NOT FIX
   "S311",    # Standard pseudo-random generators are not suitable for cryptographic purposes -- FIX ME
   "SIM905",  # Consider using a list literal instead of `str.split` -- DO NOT FIX
diff --git a/searches/quick_select.py b/searches/quick_select.py
index c8282e1fa5fc..f67f939c88c3 100644
--- a/searches/quick_select.py
+++ b/searches/quick_select.py
@@ -60,3 +60,25 @@ def quick_select(items: list, index: int):
     # must be in larger
     else:
         return quick_select(larger, index - (m + count))
+
+
+def median(items: list):
+    """
+    One common application of Quickselect is finding the median, which is
+    the middle element (or average of the two middle elements) in a sorted dataset.
+    It works efficiently on unsorted lists by partially sorting the data without
+    fully sorting the entire list.
+
+    >>> median([3, 2, 2, 9, 9])
+    3
+
+    >>> median([2, 2, 9, 9, 9, 3])
+    6.0
+    """
+    mid, rem = divmod(len(items), 2)
+    if rem != 0:
+        return quick_select(items=items, index=mid)
+    else:
+        low_mid = quick_select(items=items, index=mid - 1)
+        high_mid = quick_select(items=items, index=mid)
+        return (low_mid + high_mid) / 2
diff --git a/sorts/insertion_sort.py b/sorts/insertion_sort.py
index 46b263d84a33..2e39be255df7 100644
--- a/sorts/insertion_sort.py
+++ b/sorts/insertion_sort.py
@@ -24,7 +24,7 @@ def __lt__(self, other: Any, /) -> bool: ...
 T = TypeVar("T", bound=Comparable)
 
 
-def insertion_sort(collection: MutableSequence[T]) -> MutableSequence[T]:
+def insertion_sort[T: Comparable](collection: MutableSequence[T]) -> MutableSequence[T]:
     """A pure Python implementation of the insertion sort algorithm
 
     :param collection: some mutable ordered collection with heterogeneous
diff --git a/sorts/tim_sort.py b/sorts/tim_sort.py
index 138f11c71bcc..41ab4a10a87b 100644
--- a/sorts/tim_sort.py
+++ b/sorts/tim_sort.py
@@ -19,7 +19,7 @@ def insertion_sort(lst):
     for index in range(1, length):
         value = lst[index]
         pos = binary_search(lst, value, 0, index - 1)
-        lst = lst[:pos] + [value] + lst[pos:index] + lst[index + 1 :]
+        lst = [*lst[:pos], value, *lst[pos:index], *lst[index + 1 :]]
 
     return lst
 
diff --git a/strings/boyer_moore_search.py b/strings/boyer_moore_search.py
index 9615d2fd659b..ad14a504f792 100644
--- a/strings/boyer_moore_search.py
+++ b/strings/boyer_moore_search.py
@@ -11,23 +11,31 @@
 a shift is proposed that moves the entirety of Pattern past
 the point of mismatch in the text.
 
-If there no mismatch then the pattern matches with text block.
+If there is no mismatch then the pattern matches with text block.
 
 Time Complexity : O(n/m)
     n=length of main string
     m=length of pattern string
 """
 
-from __future__ import annotations
-
 
 class BoyerMooreSearch:
+    """
+    Example usage:
+
+        bms = BoyerMooreSearch(text="ABAABA", pattern="AB")
+        positions = bms.bad_character_heuristic()
+
+    where 'positions' contain the locations where the pattern was matched.
+    """
+
     def __init__(self, text: str, pattern: str):
         self.text, self.pattern = text, pattern
         self.textLen, self.patLen = len(text), len(pattern)
 
     def match_in_pattern(self, char: str) -> int:
-        """finds the index of char in pattern in reverse order
+        """
+        Finds the index of char in pattern in reverse order.
 
         Parameters :
             char (chr): character to be searched
@@ -35,6 +43,10 @@ def match_in_pattern(self, char: str) -> int:
         Returns :
             i (int): index of char from last in pattern
             -1 (int): if char is not found in pattern
+
+        >>> bms = BoyerMooreSearch(text="ABAABA", pattern="AB")
+        >>> bms.match_in_pattern("B")
+        1
         """
 
         for i in range(self.patLen - 1, -1, -1):
@@ -44,8 +56,8 @@ def match_in_pattern(self, char: str) -> int:
 
     def mismatch_in_text(self, current_pos: int) -> int:
         """
-        find the index of mis-matched character in text when compared with pattern
-        from last
+        Find the index of mis-matched character in text when compared with pattern
+        from last.
 
         Parameters :
             current_pos (int): current index position of text
@@ -53,6 +65,10 @@ def mismatch_in_text(self, current_pos: int) -> int:
         Returns :
             i (int): index of mismatched char from last in text
             -1 (int): if there is no mismatch between pattern and text block
+
+        >>> bms = BoyerMooreSearch(text="ABAABA", pattern="AB")
+        >>> bms.mismatch_in_text(2)
+        3
         """
 
         for i in range(self.patLen - 1, -1, -1):
@@ -61,7 +77,14 @@ def mismatch_in_text(self, current_pos: int) -> int:
         return -1
 
     def bad_character_heuristic(self) -> list[int]:
-        # searches pattern in text and returns index positions
+        """
+        Finds the positions of the pattern location.
+
+        >>> bms = BoyerMooreSearch(text="ABAABA", pattern="AB")
+        >>> bms.bad_character_heuristic()
+        [0, 3]
+        """
+
         positions = []
         for i in range(self.textLen - self.patLen + 1):
             mismatch_index = self.mismatch_in_text(i)
@@ -75,13 +98,7 @@ def bad_character_heuristic(self) -> list[int]:
         return positions
 
 
-text = "ABAABA"
-pattern = "AB"
-bms = BoyerMooreSearch(text, pattern)
-positions = bms.bad_character_heuristic()
+if __name__ == "__main__":
+    import doctest
 
-if len(positions) == 0:
-    print("No match found")
-else:
-    print("Pattern found in following positions: ")
-    print(positions)
+    doctest.testmod()
diff --git a/web_programming/current_stock_price.py b/web_programming/current_stock_price.py
index 16b0b6772a9c..531da949ea50 100644
--- a/web_programming/current_stock_price.py
+++ b/web_programming/current_stock_price.py
@@ -23,7 +23,7 @@
 def stock_price(symbol: str = "AAPL") -> str:
     """
     >>> stock_price("EEEE")
-    '- '
+    'No <fin-streamer> tag with the specified data-testid attribute found.'
     >>> isinstance(float(stock_price("GOOG")),float)
     True
     """
diff --git a/web_programming/fetch_well_rx_price.py b/web_programming/fetch_well_rx_price.py
index 93be2a9235d9..e34a89c19cc8 100644
--- a/web_programming/fetch_well_rx_price.py
+++ b/web_programming/fetch_well_rx_price.py
@@ -5,12 +5,10 @@
 
 """
 
-from urllib.error import HTTPError
-
+import httpx
 from bs4 import BeautifulSoup
-from requests import exceptions, get
 
-BASE_URL = "https://www.wellrx.com/prescriptions/{0}/{1}/?freshSearch=true"
+BASE_URL = "https://www.wellrx.com/prescriptions/{}/{}/?freshSearch=true"
 
 
 def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> list | None:
@@ -18,8 +16,8 @@ def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> list | None:
 
     This function will take input of drug name and zipcode,
     then request to the BASE_URL site.
-    Get the page data and scrape it to the generate the
-    list of lowest prices for the prescription drug.
+    Get the page data and scrape it to generate the
+    list of the lowest prices for the prescription drug.
 
     Args:
         drug_name (str): [Drug name]
@@ -28,12 +26,12 @@ def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> list | None:
     Returns:
         list: [List of pharmacy name and price]
 
-    >>> fetch_pharmacy_and_price_list(None, None)
-
-    >>> fetch_pharmacy_and_price_list(None, 30303)
-
-    >>> fetch_pharmacy_and_price_list("eliquis", None)
-
+    >>> print(fetch_pharmacy_and_price_list(None, None))
+    None
+    >>> print(fetch_pharmacy_and_price_list(None, 30303))
+    None
+    >>> print(fetch_pharmacy_and_price_list("eliquis", None))
+    None
     """
 
     try:
@@ -42,10 +40,7 @@ def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> list | None:
             return None
 
         request_url = BASE_URL.format(drug_name, zip_code)
-        response = get(request_url, timeout=10)
-
-        # Is the response ok?
-        response.raise_for_status()
+        response = httpx.get(request_url, timeout=10).raise_for_status()
 
         # Scrape the data using bs4
         soup = BeautifulSoup(response.text, "html.parser")
@@ -53,14 +48,14 @@ def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> list | None:
         # This list will store the name and price.
         pharmacy_price_list = []
 
-        # Fetch all the grids that contains the items.
+        # Fetch all the grids that contain the items.
         grid_list = soup.find_all("div", {"class": "grid-x pharmCard"})
         if grid_list and len(grid_list) > 0:
             for grid in grid_list:
                 # Get the pharmacy price.
                 pharmacy_name = grid.find("p", {"class": "list-title"}).text
 
-                # Get price of the drug.
+                # Get the price of the drug.
                 price = grid.find("span", {"p", "price price-large"}).text
 
                 pharmacy_price_list.append(
@@ -72,7 +67,7 @@ def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> list | None:
 
         return pharmacy_price_list
 
-    except (HTTPError, exceptions.RequestException, ValueError):
+    except (httpx.HTTPError, ValueError):
         return None
 
 
