Software Architecture/Engineering  

A Comparative Guide to Imperative and Declarative Programming

Declarative Programming

Programming paradigms influence the way developers think about and write code. Two of the most essential styles are imperative and declarative programming. Gaining clarity on these paradigms and knowing when to use each can greatly improve your efficiency and coding approach. In this article, we’ll explore the key differences between imperative and declarative programming, using practical examples in C# to showcase the declarative style in action, and highlight features such as LINQ.

Imperative Programming

Imperative programming is a programming style where the developer writes code that describes how a task should be performed, step by step. It focuses on changing the program’s state through statements like loops, conditionals, and variable assignments. In C#, examples include using `for` loops or `if-else` blocks to control the flow of execution. This approach gives you full control over the logic and state changes.

Key Features of Imperative Programming

  • Control Flow: The programmer explicitly defines the flow of execution using loops (for, while), conditionals (if-else), and function calls.
  • State Changes: Relies on mutable variables and step-by-step updates to manage state throughout the program.
  • Language Support: Commonly used in languages like C, C++, and also supported in C# through traditional control structures.

Imperative Programming Example with C# Code

Let’s consider a scenario where we have a list of students, each with a set of subject-wise marks. Our goal is to calculate the total marks for each student. Using imperative programming, we’ll write step-by-step instructions to loop through the data, accumulate the marks, and print the result for each student.

public class ImperativeExample
{
    static void Main(string[] args)
    {
        List<Student> students = new List<Student>();
        students.Add(new Student()
        {
            StudentId = 1,
            StudentName = "Student 1",
            studentMarks = new List<StudentMarks>()
            {
                new StudentMarks() { SubjectId = 1, Mark = 30 },
                new StudentMarks() { SubjectId = 2, Mark = 50 },
                new StudentMarks() { SubjectId = 3, Mark = 70 },
                new StudentMarks() { SubjectId = 4, Mark = 65 },
                new StudentMarks() { SubjectId = 5, Mark = 80 }
            }
        });
        students.Add(new Student()
        {
            StudentId = 2,
            StudentName = "Student 2",
            studentMarks = new List<StudentMarks>()
            {
                new StudentMarks() { SubjectId = 1, Mark = 70 },
                new StudentMarks() { SubjectId = 2, Mark = 90 },
                new StudentMarks() { SubjectId = 3, Mark = 70 },
                new StudentMarks() { SubjectId = 4, Mark = 95 },
                new StudentMarks() { SubjectId = 5, Mark = 80 }
            }
        });
        foreach (var item in students)
        {
            int total = 0;
            for (int i = 0; i < item.studentMarks.Count; i++)
            {
                total += item.studentMarks[i].Mark;
            }
            Console.WriteLine($"Student: {item.StudentName} || Total Marks: {total}");
        }
        Console.ReadKey();
    }
}

Output

Output

Declarative Programming

Declarative programming is a style where the developer describes the desired outcome, rather than detailing the exact steps to achieve it. It focuses on expressions and logic, leaving control flow to the language or framework. In C#, declarative code often utilizes features like LINQ, which enables data transformation or querying in a concise and readable manner. This approach promotes cleaner and more maintainable code.

Key Features of Declarative Programming

  • High-Level Constructs: Focuses on writing what the program should accomplish using expressions and declarations, rather than explicit step-by-step instructions.
  • Abstracted Control Flow: The control flow is managed by the language or framework, not the programmer.
  • Common Languages/Technologies: Widely seen in SQL, HTML, CSS, and functional programming languages like Haskell. In C#, LINQ is a strong example of a declarative approach.

Declarative Programming Example with C# Code

Now, let’s achieve the same goal by calculating total marks for each student using a declarative approach. Instead of manually looping and updating totals, we’ll use LINQ in C# to express what we want: the sum of each student’s marks. The underlying logic, like iteration and aggregation, is handled by the framework, resulting in cleaner and more concise code.

public class DeclarativeExample
{
    static void Main(string[] args)
    {
        List<Student> students = new List<Student>
        {
            new Student
            {
                StudentId = 1,
                StudentName = "Student 1",
                studentMarks = new List<StudentMarks>
                {
                    new StudentMarks { SubjectId = 1, Mark = 30 },
                    new StudentMarks { SubjectId = 2, Mark = 50 },
                    new StudentMarks { SubjectId = 3, Mark = 70 },
                    new StudentMarks { SubjectId = 4, Mark = 65 },
                    new StudentMarks { SubjectId = 5, Mark = 80 }
                }
            },
            new Student
            {
                StudentId = 2,
                StudentName = "Student 2",
                studentMarks = new List<StudentMarks>
                {
                    new StudentMarks { SubjectId = 1, Mark = 70 },
                    new StudentMarks { SubjectId = 2, Mark = 90 },
                    new StudentMarks { SubjectId = 3, Mark = 70 },
                    new StudentMarks { SubjectId = 4, Mark = 95 },
                    new StudentMarks { SubjectId = 5, Mark = 80 }
                }
            }
        };
        var studentTotals = students.Select(s => new
        {
            s.StudentName,
            TotalMarks = s.studentMarks.Sum(m => m.Mark)
        });
        foreach (var student in studentTotals)
        {
            Console.WriteLine($"Student: {student.StudentName} || Total Marks: {student.TotalMarks}");
        }

        Console.ReadKey();
    }
}

Output

Console App

In the example discussed, imperative programming gives full control over logic, allowing us to add if-else conditions, such as checking if a student’s mark is below 35 and applying grace marks to help them pass. In contrast, declarative programming (like LINQ in C#) focuses on what to compute, making it harder to embed such conditional logic inline, especially when decisions depend on step-by-step state changes.

Imperative Example with Grace Mark Logic

public class ImperativeExample
{
    static void Main(string[] args)
    {
        List<Student> students = new List<Student>();
        students.Add(new Student()
        {
            StudentId = 1,
            StudentName = "Student 1",
            studentMarks = new List<StudentMarks>()
            {
                new StudentMarks() { SubjectId = 1, Mark = 30 },
                new StudentMarks() { SubjectId = 2, Mark = 50 },
                new StudentMarks() { SubjectId = 3, Mark = 70 },
                new StudentMarks() { SubjectId = 4, Mark = 65 },
                new StudentMarks() { SubjectId = 5, Mark = 80 }
            }
        });
        students.Add(new Student()
        {
            StudentId = 2,
            StudentName = "Student 2",
            studentMarks = new List<StudentMarks>()
            {
                new StudentMarks() { SubjectId = 1, Mark = 70 },
                new StudentMarks() { SubjectId = 2, Mark = 90 },
                new StudentMarks() { SubjectId = 3, Mark = 70 },
                new StudentMarks() { SubjectId = 4, Mark = 95 },
                new StudentMarks() { SubjectId = 5, Mark = 80 }
            }
        });
        foreach (var student in students)
        {
            int total = 0;

            foreach (var mark in student.studentMarks)
            {
                int actualMark = mark.Mark;

                // Apply grace marks if mark is below 35
                if (actualMark < 35)
                {
                    Console.WriteLine($"Subject {mark.SubjectId}: Grace applied to {actualMark}");
                    actualMark += 5; // Adding grace marks
                }
                total += actualMark;
            }
            Console.WriteLine($"Student: {student.StudentName} || Total Marks (with grace): {total}");
        }

        Console.ReadKey();
    }
}

Output

Debug

Imperative vs. Declarative Programming – Comparison Table

Aspect Imperative Programming Declarative Programming
Approach to Problem Solving Describes how to perform tasks step by step Describes what result is needed without detailing steps
Handling Execution Flow Control flow is manually defined using loops and conditionals Control flow is handled by the language or framework
Way of Managing Data and State Relies on mutable variables and explicit state changes Prefers immutability and abstracts state management
Level of Code Abstraction Lower-level, closer to how the computer processes instructions Higher-level, focused on logic and desired outcomes

Conclusion

Both imperative and declarative programming paradigms offer unique strengths, and understanding when to use each can significantly improve the clarity, efficiency, and maintainability of your code. Declarative approaches—such as using LINQ in C#—allow you to focus on the what rather than the how, reducing boilerplate and improving readability.

By exploring and practicing both paradigms, you'll become a more adaptable and effective developer. Choose the style that best fits your problem, and let your code speak clearly.