The curious connection between programming languages and data storage technologies
Programming languages and storage technologies solve different problems, but they achieve their goals by sharing some fundamental approaches and concepts.
Generally speaking, programming languages focus on the computation and manipulation of information, while on the other hand, storage technologies focus on the retention of such information for a long period of time. Even though the goal of these two concepts is significantly different, it is fascinating to see how they both share very similar approaches in delivering their technological solutions.
It is fair to say that storage technologies use programming languages to fulfill their goals — in fact the majority of storage technologies have been built using imperative, statically-typed languages. But what I find curious is that storage technologies expose and deliver their solutions through the very same concepts you find across the broad spectrum of programming languages.
Note: the classifications below are deliberately broad. Most languages and databases borrow from more than one of these groups. The point here is the resemblance, not the taxonomy.
Imperative
This paradigm is part of the broad classification of programming languages. It takes the approach of using steps, or a series of statements, to manipulate the state of a program. Imperative programming encompasses a very wide spectrum of further classifications, but I’ll focus here on the distinction that maps most cleanly onto storage — the one between statically-typed and dynamically-typed languages. They split along the question of when do you commit to the shape of your data, regardless of whether that data is stored temporarily in memory or permanently in storage.
Structured
This group focuses on strictly defined foundational building blocks and the relationships between them. When it comes to programming languages, this concept is closely related to statically-typed languages, while with storage technologies, this is usually the world of relational databases.
It feels like building bottom-up — you define the structure of your information first, decide how it is supposed to flow, and only then write the code around it. The critical concept here, is that the structure is the contract, and the code is written to honor it.
| Statically-typed languages | Relational databases | |
|---|---|---|
| Examples | C, C++, Java, Rust, Scala, Go | Oracle, MySQL, SQL Server, PostgreSQL |
| Structure | Along with classes and functions, you have strictly defined variable types | Along with schemas and tables, you have strictly defined column data types |
| Declaration | Before the program runs | Before row data exists |
| Enforcement | The compiler refuses to build | The database engine refuses to write |
| Violation example | A string assigned to an int | A string written to an INTEGER column |
This approach is rigid and it has its advantages. The structure is known and enforced ahead of usage, so you can reason about the code and the data with confidence. Furthermore, you can rely on the structure to be consistent, which has the tendency to reduce bugs and other issues. Such a foundation usually leads to better performance and speed. Because of the strict definitions, the code can be compiled more efficiently and ahead of time, which is usually the case. The usual trade-off is that you have to commit to a shape up front, and changing it later is usually costly.
Flexible
This group focuses on loosely defined foundational building blocks and the relationships between them. When it comes to programming languages, this concept is closely related to dynamically-typed languages, while with storage technologies, this is usually the world of document databases.
This approach feels more like building top-down. You declare your structure in code, then store it however you wish. The focus shifts away from the shape of the data and toward the execution itself, and the critical concept here is the mirror image of the previous one: the code is the contract, while the structure is whatever the code happens to produce.
| Dynamically-typed languages | Document-based databases | |
|---|---|---|
| Examples | Python, JavaScript, PHP, Ruby, Perl | MongoDB, Elasticsearch, DynamoDB |
| Structure | Along with classes and functions, variables can have varying structure and types | Along with schemas and tables, documents can have varying structures and types |
| Declaration | While the program runs | As each document is written |
| Enforcement | At runtime, the moment of use | Your code, when it reads the document back |
| Violation example | Calling a method the object never got | Reading a field the document never had |
This approach is flexible and it also has its advantages. The structure and data can be easily changed, which is very useful for rapid development and prototyping, as well as software and data evolution. Furthermore, there is a greater potential of code and data reuse, because you can use the same code or document to handle different shapes. The usual trade-off is increased complexity in managing and maintaining the system, since you have to be aware of the shape of the data at runtime, and you have to handle the possibility of missing fields or unexpected types. This is usually addressed by introducing additional code to validate the data. However, this additional code can be a source of more bugs and other issues, and it can also slow down the execution of the program. Shifting the focus away from the structure and toward the execution can also lead to performance issues (usually the case), since the code and data are interpreted at runtime.
Declarative
This is a different paradigm altogether, and the one I find most interesting in this comparison. Where imperative programming describes a series of steps, declarative programming is outcome-focused — you describe what you want and the relationships involved, and leave the how to the runtime. The question is no longer when to commit to a structure, but how much of the execution should be yours to control.
This group focuses on describing transformations and relationships between values, rather than mutating state step by step. When it comes to programming languages, this is the world of functional languages, while with storage technologies, it is closest to the world of graph databases, where relationships are stored and traversed directly.
This approach feels more like building from the relationships outward. You describe the values, transformations, and connections that define the result, then let the runtime determine the sequence of operations that produces it. The critical concept here is that the relationship is the contract, while the execution is an implementation detail.
| Functional languages | Graph databases | |
|---|---|---|
| Examples | Haskell, Lisp, Erlang, F# | Neo4j, Dgraph, Amazon Neptune |
| Structure | Values are combined through functions and expressions, with an emphasis on composition and immutable data | Nodes are connected by explicitly stored edges that represent relationships |
| Declaration | By describing transformations and the result they should produce | By describing the nodes, relationships, and paths that make up the desired result |
| Enforcement | The language runtime and its evaluation model determine how expressions are executed | The database engine determines how to traverse and optimize the graph |
| Violation example | A function receives a value it cannot meaningfully transform | A requested path does not exist in the graph |
This approach shifts complexity away from the instructions and into the relationships being described. Functional languages let you compose small, predictable transformations and reason about what they produce without tracking every state change. Graph databases make connections first-class, so a query can follow the relationship itself instead of reconstructing it from separate records. In both cases, the runtime has room to choose an efficient execution strategy — evaluation order for expressions, traversal and indexes for graph queries.
The usual trade-off is that the execution can become harder to see and control. A functional program may require a different way of thinking about state, recursion, and side effects. A graph query may be wonderfully direct when the relationships are central, but less natural when the problem is mostly tabular aggregation or rigid reporting. The more you delegate to the runtime, the more important it becomes to understand the model it uses to turn a declaration into a result.
The blending reality
While these technologies wear the paradigm most openly, you can use declarative concepts in plenty of imperative languages and storage systems too — SQL inside Java, LINQ in C#, a pipeline of map and filter in JavaScript, or a graph-shaped model in a relational database. The idea travels.
Modern tools blur every line drawn above. PostgreSQL stores schemaless JSON in a JSONB column; MongoDB will validate a schema if you ask it to; TypeScript bolts static checking onto JavaScript. The mapping describes where each technology leans, and what it makes easy versus what it makes possible — not a rule any single product obeys completely.
The shape you choose
Zoom out and the two worlds stop looking like a coincidence. Whether you are reaching for a language or a storage technology, you are really answering the same question: how much do you want structure to lead, and how much do you want it to follow?
Commit to the structure first and let the tool enforce it, and you get the guarantees of static types and relational schemas — safety, verified relationships, fewer surprises at runtime, paid for with rigidity up front. Let the structure follow the code and stay malleable, and you get the freedom of dynamic types and document stores — speed, flexibility, the ability to reshape as you go, paid for with fewer guarantees. Make the relationship itself the thing you declare, and you land in the world of functional languages and graph databases, where the outcome matters more than the steps that produce it.
None of these is the right answer in isolation — they’re trade-offs, and the interesting part is how consistently the same trade-off shows up on both sides of the line between computation and retention. Two technologies built for opposite goals, arriving at the same small set of answers.