Programming Concepts

Programming Concepts
Photo by Kevin Canlas / Unsplash

Meaning

Programming language is a medium for communication by human with machine.

We write codes for e.g in Java(High Level) but machine can't understand it so we need to convert it into low level language (Binary language($0, 1$)) so that machine can understand it.
Compilers and interpreters translates high level language into the low level language.

  • Interpretor: translates(converts) high language line by line into binary language.
  • Compiler: translate the full high level language into binary language at once.

Styles of programming:

  1. Imperative:
    In this programming we need to define how to do something or how to achieve the goal.
  2. Declarative:
    In Declarative programming we need to define what to do to achieve something not how to do that thing.

Dynamic and Static Typing

  1. Dynamic Typing
    In this, variables names automatically guess or derive the type of variable from the current value.
    x = 15 # it's integer
    y = False # it's boolean
    
  2. Static Typing
    In this, we are explicitly required to mention the type of variable.
    int z = 15 // it's integer
    

Scope and Lifetime of variables

  1. Scope of variables:
    • when the variables are available for use.
    • In below code we can't access variable x outside the function
    def fun(x):
    	y = x+1
    	return y
    
  2. Lifetime of variables:
    • How long the storage remains allocated.
    • in above code, x is only available until the function runs.
    • Hole in scope:

    Variable is alive but not in scope.

Memory Management

An activation record is what gets pushed onto the stack when a function is called.
it stores everything the function needs to run.
it:

  • Popped popped when function exists
  • control link point to start of previous record
  • Return value link where to store result
    Scope of variables:
  • Variables in activation record is located at top of stack
  • Access global variables by following control links
    ![[Screenshot_20260206_015200_Visha Player.png]]

Heap Memory Management

Heap memory doesn't auto-delete like stack memory does. When you create data on the heap, it stays there forever until explicitly freed, creating dead storage (unreachable memory).

Stack vs Heap

Stack: Variables automatically deallocated when function exits.
Heap: Memory persists until manually freed, even if unreachable.

Manual Memory Management (C/C++)

Programmer controls memory with malloc() to allocate and free() to deallocate. Risk: Memory leaks and invalid assignments if forgotten.

Automatic Garbage Collection (Java, Python)

Runtime environment automatically cleans dead storage using mark-and-sweep algorithm:

  • Mark phase: Identify all reachable memory from program variables
  • Sweep phase: Return all unmarked (unreachable) memory to free space

Trade-off

Manual = Full control + better performance, but error-prone.
Automatic = Convenient for programmer, but performance penalty during GC cycles.

Terms

  • Memory leak: Allocated memory that's no longer used but not freed
  • Dead storage: Memory that exists but is unreachable from program
  • Garbage collection: Automatic process to reclaim unused heap memory

Call by Value and Call by reference

Call by value and call by reference are NOT methods you choose - they describe HOW the programming language handles memory when passing arguments!

  1. Call by value
    In this a copy of data is created in memory, the function works with the copy and original data is in different memory location.

  2. Call by reference
    In this memory reference(Address) is passed and function works with thar memory location by preserving the original data.

Stepwise Refinements

Refinement means breaking down bigger problem into smaller steps then breaking those into even more smaller steps.

  1. Program Refinement

    Improving the logic and structure of your code to make it better, cleaner, reusable.

    Before (Rough version):

sum = 0
for i in range(1, 11):
    if i % 2 == 0:
        sum = sum + i
print(sum)

After (Refined version):

def sum_even_numbers(start, end):
    """Calculate sum of even numbers in range"""
    return sum(num for num in range(start, end + 1) if num % 2 == 0)
result = sum_even_numbers(1, 10)
print(f"Sum of even numbers: {result}")
  1. Data Refinement
    Changing how you store and organize the data to make it more efficient and appropriate.

Before refinement

# Storing student info as separate lists
student_names = ["Alice", "Bob", "Charlie"]
student_ages = [20, 21, 19]
student_grades = [85, 90, 88]
# Hard to manage! What if order gets messed up?

After refinement

# Using a class (most professional!)
class Student:
    def __init__(self, name, age, grade):
        self.name = name
        self.age = age
        self.grade = grade
students = [
    Student("Alice", 20, 85),
    Student("Bob", 21, 90),
    Student("Charlie", 19, 88)
]

Modular Software Development

Refinement is Starting with a high-level problem then decomposing it into smaller, manageable components (e.g., functions, modules, classes).

Prototype

Creating a simple version to test if the design works before full implementation.
Example: Prototype a "sorting algorithm" component to verify it handles edge cases (e.g., empty lists).

Components are divided into two things

  1. Components

What other components "see": Function names, input arguments, return types (e.g., calculateTotal(items: List[Price]) -> float).

  1. Specifications

What the component does: High-level behavior (e.g., "Sorts a list of integers in ascending order").
Hides implementation details (e.g., whether it uses QuickSort or MergeSort).

Benefits

Independent Improvement
Enhance or fix one component at a time without affecting others, as long as its interface (external contract) and specification (behavior) remain unchanged.

Programming with objects

Variables = Storage boxes for data (like age = 5, name = "John").
Methods = Functions that belong to a class (actions an object can do).
Objects = Complete packages created from a class that contain both variables AND methods.

class Car:
    # These are VARIABLES (also called attributes/properties)
    color = "red"
    speed = 0
    
    # This is a METHOD (function inside the class)
    def drive(self):
        print("Car is driving")
# Creating an OBJECT
my_car = Car()  # <-- my_car is the OBJECT

Features of OOP

Abstraction

Abstraction means hiding complex details and showing only what we need.
Like a car: you don't need to know engine details, just press gas pedal.

Subtyping

Creating variations(different types) of objects.
Like different types of cars: sedan, SUV, truck - all are cars

# Stack: Add/remove from ONE end
# Queue: Add to one end, remove from other
# Dequeue: Add/remove from BOTH ends (more powerful!)~~

Subtype means CAN-BE-USED-AS relationship
Subtyping means:
Type A can do everything Type B can do.
Type A might have extra features too.
Wherever you need B, you can use A instead.
Example:

class Employee:
    def work(self):
        print("Working")
class Manager(Employee):  # Manager is SUBTYPE of Employee
    def work(self):
        print("Managing")
    def hold_meeting(self):  # Extra feature
        print("Meeting")

Why Manager is subtype of Employee:

  • Manager IS-A Employee
  • Manager can do everything Employee can (has work() method)
  • Manager has extra stuff too (hold_meeting())
  • Anywhere you need an Employee, you can use a Manager

Dynamic lookup

Decide which method to call at runtime
Like GPS recalculating route based on current traffic

Inheritance

Child classes get properties from parent classes
Like you inherit traits from your parents

Abstract Data type

An Abstract Data Type (ADT) defines a data type by its behavior (what it does) rather than its implementation (how it does it). It specifies:

  • What operations can be performed on the data.
  • What those operations do (their semantics).
  • What values the data type can hold.

Key Characteristics of ADTs:

  1. Encapsulation: The internal details (implementation) are hidden from the user.
  2. Abstraction: The user interacts with the ADT through a well-defined interface, without needing to know how it works internally.
  3. Reusability: ADTs can be implemented in different ways (e.g., using arrays, linked lists, etc.) without changing how they are used.

Examples of Common ADTs:

ADT Description Common Operations
List A collection of elements in a specific order. Insert, delete, search, traverse.
Stack A collection where elements are added and removed in a "last-in, first-out" (LIFO) order. Push, pop, peek.
Queue A collection where elements are added at the rear and removed from the front ("first-in, first-out" or FIFO). Enqueue, dequeue, peek.
Dictionary A collection of key-value pairs. Insert, delete, search (by key).
Tree A hierarchical structure with nodes connected by edges. Insert, delete, traverse (in-order, pre-order, post-order).

Why Use ADTs?

  • Modularity: You can change the implementation without affecting the rest of the program.
  • Clarity: Focuses on what the data type does, not how it does it.
  • Flexibility: Allows for multiple implementations (e.g., a stack can be implemented using an array or a linked list).

Example in Python:
Python’s built-in list is an implementation of the List ADT, but you can also implement your own stack using a list:

class Stack:
    def __init__(self):
        self.items = []
    def push(self, item):
        self.items.append(item)
    def pop(self):
        return self.items.pop()
    def peek(self):
        return self.items[-1]
# Usage
s = Stack()
s.push(10)
s.push(20)
print(s.pop())  # Output: 20