Skip to content
DevPebble
Programming Tutorials

Declarative programming explained: how it works, with real examples

Declarative programming explained — what it is, how it works, declarative vs imperative code, core principles, languages and tools, examples, and when to use it.

The DevPebble Team11 min read
Declarative programming explained with real examples — describing the result you want in SQL, CSS, React, and Terraform while an engine works out the steps.
declarative programmingwhat is declarative programmingdeclarative vs imperative programmingdeclarative programming examplesdeclarative programming languages
On this page

Open a file you wrote six months ago and find fifteen nested loops sitting on a pile of mutable variables, and you'll spend the first ten minutes just working out what the code was trying to do. That gap between what code does and what it says is the everyday cost of spelling out every step by hand. Declarative programming closes that gap. Instead of writing the instructions, you describe the result and let a system work out the steps.

That single idea runs underneath some of the most widely used tools in software: SQL queries, CSS stylesheets, React components, and infrastructure tools like Terraform. This guide covers what declarative programming is, how it works, how it differs from the imperative style most people learn first, and when each one is the right call. By the end you'll have a mental model you can apply the next time you're deciding how to structure a piece of logic.

What is declarative programming?

What is declarative programming — describing the result you want and letting a compiler, interpreter, or query engine work out the steps.

Declarative programming is a paradigm where you describe what result you want and leave the how, the exact sequence of steps, to something else: a compiler, an interpreter, a query optimizer, or a rendering engine. You write a specification of the finished result rather than a recipe of numbered steps.

A SQL query is the clearest example:

SELECT name, email FROM customers WHERE signup_date > '2025-01-01';

Nothing in that statement tells the database how to scan tables, which index to use, or in what order to filter rows. You describe the shape of the data you want, and the query planner works out the fastest route to it. That is declarative code in one line: intent as a specification, execution handled elsewhere.

The same idea turns up in places people don't always file under "programming." CSS is declarative: you write display: flex and the browser calculates the pixel positions. HTML is declarative: you describe a document's structure and the browser builds the DOM. Regular expressions are declarative: you describe a pattern and the engine handles the matching.

How the abstraction layer works

How the declarative abstraction layer works — an interpreter between your code and the machine that turns what you want into how it runs.

What makes this possible is an abstraction layer sitting between your code and the machine. In imperative code you talk almost directly to the machine's execution model: assign this variable, loop through this array, change this object. In declarative code you talk to an interpreter that has its own understanding of how to turn what into how.

This is why the same SQL query can run differently and still return the same result depending on the engine, the available indexes, or the size of the dataset. The declaration stays fixed while the execution strategy is free to change. That separation is the whole point, and it's what lets the system underneath improve without you rewriting anything.

Declarative vs imperative programming: what's the difference?

Declarative vs imperative programming — describing the outcome and letting the engine run it versus writing explicit, ordered steps that change state.

Imperative programming is the style most people meet first, in languages like C or Python written in a traditional procedural style. You write explicit, ordered instructions: set up this variable, loop while this condition holds, change this value, check that one, repeat.

Summing an array imperatively looks like this:

let total = 0;
for (let i = 0; i < numbers.length; i++) {
  total += numbers[i];
}

The declarative version:

const total = numbers.reduce((sum, n) => sum + n, 0);

Both return the same number. The imperative version tells the machine exactly how to get there: create a counter, check a condition, change state, repeat. The declarative version describes the transformation and lets reduce handle the looping.

The difference isn't only stylistic. It changes how you reason about the code. Imperative code makes you trace state as it changes across iterations. Declarative code asks you to think about the relationship between input and output, with no intermediate changes to track.

| Aspect | Imperative programming | Declarative programming | |---|---|---| | Core question | How do I do this? | What do I want? | | Control flow | Explicit loops, conditionals, sequencing | Handled by the underlying engine | | State | Mutable variables, step-by-step updates | Favors immutability over mutation | | Focus | Process and execution order | Outcome and logic | | Readability at scale | Harder to follow as logic grows | Tends to stay readable since intent is explicit | | Debugging | Trace variable state across steps | Trace transformations and expected outputs | | Examples | C, traditional Java or Python loops, assembly | SQL, HTML and CSS, React, Terraform, Haskell |

Neither style is better in every case. Imperative programming gives you fine control, which matters when you're optimizing a hot loop or working close to hardware. Declarative programming trades some of that control for clarity and less boilerplate, which matters when you're maintaining a large system with many contributors.

Most real codebases mix both. A React app is declarative at the component level but usually holds imperative logic inside event handlers. A Python pipeline might use declarative list comprehensions next to imperative file handling. Knowing both means you pick the right tool for each piece of logic instead of forcing everything into one mold.

Core principles of declarative programming

Core principles of declarative programming — abstraction over implementation, immutability, expressions over statements, and composability.

Declarative programming isn't a single technique. It's a set of principles that together give the code a distinct character, and that explain why it tends to be easier to test and less prone to certain bugs.

Abstraction over implementation

Abstraction over implementation — declaring a desired end state, like three running replicas, and letting the underlying system make it happen.

The defining trait is that implementation details get pushed down into an underlying system so your code can express intent. Declare in a Kubernetes manifest that a service needs three running replicas and you're not writing the logic that starts containers, checks their health, or restarts the failed ones. That lives in the Kubernetes control loop. Your job is to describe the desired end state.

The payoff is practical. When the engine underneath improves, say a faster query optimizer or a smarter reconciliation loop, your code gets the benefit without a rewrite.

Immutability and predictable state

Immutability and predictable state — producing new values from transformations instead of reassigning variables, so a value stays what it is once created.

Declarative code leans on immutability, values that don't change after they're created, rather than variables reassigned over and over. Functional-style code produces new values from transformations instead of changing existing ones in place.

This matters because mutable state is one of the most common sources of hard-to-trace bugs. When a variable can change from a dozen places, knowing its value at any moment means tracing every path that touches it. An immutable value, once created, stays what it is.

Expressions over statements

Expressions over statements — composing small pieces of code that evaluate to a value instead of chaining side-effecting instructions.

Imperative code is built from statements, instructions run for their side effects: assign this, print that, change this object. Declarative code favors expressions, pieces of code that evaluate to a value and can be composed. Working in expressions nudges you toward small, composable pieces rather than long runs of side-effecting instructions. It's also why functional and declarative programming overlap so much: a pure function, whose output depends only on its input, describes a transformation rather than a procedure.

Composability

Composability in declarative programming — chaining small, reusable expressions like filter, map, and sort that read almost like a sentence.

Because declarative code is built from small expressions, it composes well. A chain like data.filter(isActive).map(toDisplayName).sort() reads almost like a sentence, and each link can be tested and reused on its own. The imperative equivalent, one loop juggling filtering, transformation, and sorting in shared state, tangles those jobs together, so changing one risks breaking the others.

This is also why declarative UI frameworks scaled. Rather than hand-writing DOM updates for every state change, you describe the interface as a function of state and let the framework handle the updates. That shift is a big part of why the declarative model now dominates modern front-end work.

Declarative programming languages and tools

Declarative programming languages and tools — SQL, Haskell, Prolog, GraphQL, Terraform, Kubernetes, HTML, and CSS, each declaring an outcome for its domain.

Once you start looking, declarative thinking shows up across some of the most used tools in the field. Each applies the same idea to a different problem.

SQL is the clearest entry point. Every relational database, including PostgreSQL, MySQL, and SQL Server, runs on declarative queries. An analyst doesn't loop over every order row looking for matches; they write a query describing the result set they need, and the planner picks the route based on indexes and table statistics.

Haskell takes the idea to its strictest form. It's a purely functional language where functions behave like mathematical definitions rather than command sequences. Some financial and fintech teams have used it for transaction systems because a side-effect-free style rules out whole categories of state-mutation bugs, which matters when the state in question is someone's money.

Prolog works differently again. You declare facts and rules, and the inference engine works out how to answer queries against them. It has a long history in expert systems, natural-language research, and rule-based reasoning, wherever a problem is easier to state as logic than as procedure.

GraphQL brought the pattern to the API layer. A client sends one query describing exactly which fields it needs, and the server resolves that shape in a single round trip, which helps teams avoid over-fetching and under-fetching data.

Terraform, CloudFormation, and Kubernetes manifests apply the same philosophy to infrastructure. A platform engineer declares the desired state, "four replicas of this service behind this load balancer, using this storage class," and the tool reconciles the live environment to match, rather than scripting each provisioning step by hand.

| Language or tool | Domain | What you declare | |---|---|---| | SQL | Databases | The shape of the result set you want | | Haskell | Functional, general purpose | Pure transformations between input and output | | Prolog | Logic and reasoning systems | Facts and rules the engine can query | | GraphQL | API data fetching | The exact fields and structure of a response | | Terraform, Kubernetes | Infrastructure and DevOps | The desired end state of a system | | HTML, CSS | Web structure and styling | Document structure and visual presentation |

What ties these together isn't syntax. It's the same discipline: describe an outcome, then trust a purpose-built engine to run it.

Declarative programming examples for beginners

Declarative programming examples for beginners — ordering food, finding an item, sorting, centering a box in CSS, and a SQL join, each stating the outcome.

If the idea still feels abstract, a plain comparison helps. Ordering food at a restaurant is declarative. You say "a pizza with mushrooms, no onions." You don't tell the kitchen which pan to use or how long to preheat the oven. You state the outcome and someone else runs the process. Cooking it yourself, step by step, is the imperative version.

Here's the same shift in code, in examples simple enough to try if you're new to this.

Finding an item in a list. Imperatively you write a loop:

found = None
for item in items:
    if item == "apple":
        found = item
        break

Declaratively in Python you describe what you want:

found = next((item for item in items if item == "apple"), None)

You're stating the target, an item equal to "apple", instead of managing a counter and a break.

Sorting numbers. Rather than writing a sort by hand, you declare the order:

sorted_numbers = sorted(numbers, reverse=True)

You aren't implementing quicksort. You're telling Python what you want, largest to smallest, and the built-in handles it.

Styling on a page. Beginners often meet declarative thinking in CSS without noticing:

.box {
  margin: 0 auto;
}

One line declares "keep this box horizontally centered." No measuring the container by hand.

Pulling values out of a list:

const prices = products.map((product) => product.price);

That line says "give me the prices from this list." It reads close to plain English, which is the point.

The same pattern scales up. A SQL join that aggregates results hides all the matching and counting work:

SELECT department, COUNT(*) AS total_employees
FROM employees
JOIN departments ON employees.dept_id = departments.id
GROUP BY department;

Nothing here says how to match rows or accumulate the counts. The engine decides whether to use a hash join or a nested loop and which index to scan first. From a one-line CSS rule to a multi-table query, the discipline holds: describe the outcome, let the engine build it.

Benefits of declarative programming

Benefits of declarative programming — shorter code, fewer state bugs, easier collaboration, safe parallelism, and free gains from engine improvements.

The appeal isn't theoretical. It shows up in daily work once you've lived with it for a while.

It shortens the distance between intent and code. Open a file of SELECT statements or JSX components and you can usually guess what it does without simulating a run of state changes. That lower cognitive load compounds across a codebase: reviews go faster, onboarding is quicker, and misreading intent causes fewer bugs.

It cuts boilerplate. Declaring what a UI should look like for each state and letting a framework handle the diffing is almost always shorter than hand-writing DOM updates, and shorter code has fewer places for bugs to hide.

It removes a class of state bugs. A large share of hard-to-reproduce bugs trace back to mutable state changed from somewhere unexpected. Styles that favor immutability and pure expressions sidestep that by design, since there's less shared, mutable state to go wrong with.

It travels across skill levels. SQL and HTML are approachable enough that analysts, marketers, and designers can read and sometimes write them directly. That lowers the barrier to collaboration in a way heavily procedural code rarely does.

It opens the door to safe parallel work. Because declarative expressions often avoid shared mutable state, many can run at the same time without risking race conditions. That's part of why these patterns are common in systems that crunch large datasets across many cores or machines.

It benefits from engine improvements for free. When a database ships a smarter optimizer or a framework improves its rendering, declarative code written years earlier gets faster without a line changing. Hand-tuned imperative code doesn't get that for free.

Limitations and challenges of declarative programming

Limitations and challenges of declarative programming — black-box debugging, unpredictable performance, leaky abstractions, a learning curve, and problems that don't fit.

Declarative programming has real trade-offs, and using it well means being honest about them.

Debugging can feel like a black box. When something breaks inside an abstraction you don't control, a query that slows down after a data spike or a CSS layout that misbehaves across browsers, you're often debugging the engine's decisions rather than your own code. Working out why a planner chose a particular join order can take knowledge well beyond the declarative syntax itself.

Performance is harder to predict. In imperative code you know how many operations run because you wrote each one. In declarative code the execution path is chosen for you, so performance can shift under load without any change on your side. Performance-critical systems often still need imperative, low-level tuning that declarative abstractions aren't built to offer.

Abstractions leak. These tools promise to hide the details, but using them well often means knowing the details anyway. Efficient SQL still requires understanding indexes. Fast CSS layouts still benefit from knowing how the browser renders a page. The abstraction lowers how often you need that knowledge without removing the need for it.

There's a learning curve. Thinking in transformations rather than steps is a genuine shift. Developers used to loops and mutable variables can find functional, declarative patterns awkward at first, and reasoning in that style takes deliberate practice.

Not every problem fits. Highly stateful logic, such as game loops, real-time simulations, and low-level device drivers, often needs the step-by-step control imperative code gives you. Force a declarative model onto a problem that's naturally procedural and you can end up with something more tangled than the imperative version it was meant to replace.

When to use declarative vs imperative programming

When to use declarative vs imperative programming — signals for reaching for each approach across data, UIs, infrastructure, algorithms, and hardware.

Choosing between them isn't about picking a correct side. It's about matching the approach to the problem in front of you. A few signals help.

Reach for declarative programming when:

  • You're transforming data, filtering, mapping, aggregating, or querying, where describing the output shape beats tracking each step.
  • You're building user interfaces, where describing what the screen should show for a given state is easier to maintain than hand-updating the DOM.
  • You're managing infrastructure or configuration, where declaring the end state with Terraform or Kubernetes is safer than scripting each provisioning step.
  • Readability and long-term maintenance matter more than micro-level tuning, especially on larger or mixed-skill teams.
  • You want fewer state-related bugs, particularly in concurrent code.

Reach for imperative programming when:

  • You need precise control over execution order, memory, or performance, which is common in systems, embedded, or performance-critical code.
  • You're writing a custom algorithm where the exact sequence of operations is the point, not just a means to an output.
  • You're working close to hardware or under tight resource limits, where every operation has to be accounted for.
  • The available declarative abstractions don't fit the problem, and forcing one would make the code harder to follow rather than easier.

| Scenario | Better fit | |---|---| | Querying or transforming data | Declarative | | Building a UI from application state | Declarative | | Managing cloud infrastructure | Declarative | | Writing a custom sorting or pathfinding algorithm | Imperative | | Working with hardware-level or embedded code | Imperative | | Optimizing a performance-critical inner loop | Imperative |

Most projects don't sit entirely on one side. A well-built app often declares its UI and data fetching while relying on a few carefully written imperative functions for the parts that genuinely need fine control. Spotting which category a piece of logic belongs to is a skill that sharpens with experience, and it's more useful than treating either paradigm as a rule for everything.

Conclusion

Declarative programming in conclusion — describing the outcome and handing the how to a purpose-built system, while keeping imperative code for fine control.

Declarative programming moves your focus from writing out every step to clearly describing the outcome, then hands the how to a system built for that job. It runs under SQL queries, CSS layouts, React components, and infrastructure tools like Terraform, and it brings cleaner code, fewer state-related bugs, and easier collaboration, while leaving imperative programming for the cases where fine control genuinely matters.

The point was never to drop imperative thinking. It's to notice when describing the destination is worth more than scripting every step of the trip. Developers who hold both approaches in mind, and know which one a problem calls for, tend to write clearer and more reliable software than those who treat either as always right.

Frequently asked questions

Quick answers to the questions developers ask most about this topic.

What is declarative programming in simple words?

It's a style of coding where you describe the result you want and let the system work out how to produce it.

What is an example of declarative programming?

A SQL query is a common one: you describe the data you want, and the database decides how to fetch it. CSS and React components follow the same pattern.

Is SQL a declarative language?

Yes. SQL queries describe the data you want rather than the steps to fetch it, which makes it one of the most widely used declarative languages.

Is HTML declarative programming?

Yes, HTML follows the declarative model: it describes a document's structure and the browser handles building it. Strictly speaking, HTML is a markup language rather than a programming language, but the declarative idea is the same.

What's the main difference between declarative and imperative programming?

Declarative code focuses on what outcome you want. Imperative code focuses on how to get there, step by step.

Is React declarative or imperative?

React is declarative. Components describe what the UI should look like for a given state, and React updates the DOM to match.

Is declarative programming the same as functional programming?

No, but they overlap. Functional programming is one way to write declarative code, since pure functions describe transformations. You can also write declarative code in SQL, HTML, or CSS, which aren't functional languages.

Can you use declarative and imperative programming together?

Yes. Most real applications mix them, using declarative code for structure and data flow and imperative code where fine control is needed.

Is declarative programming harder to learn?

It can feel less intuitive at first if you're used to loops and step-by-step logic, but it often becomes easier to read and maintain with practice.

Keep reading

Have a project in mind? Let's build it.

Tell us what you're working on. We'll reply within one business day with honest, practical next steps — no pressure, no jargon.