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.

Programming languages and storage technologies

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

Structured data

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 languagesRelational databases
ExamplesC, C++, Java, Rust, Scala, GoOracle, MySQL, SQL Server, PostgreSQL
StructureAlong with classes and functions, you have strictly defined variable typesAlong with schemas and tables, you have strictly defined column data types
DeclarationBefore the program runsBefore row data exists
EnforcementThe compiler refuses to buildThe database engine refuses to write
Violation exampleA string assigned to an intA 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

Flexible data

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 languagesDocument-based databases
ExamplesPython, JavaScript, PHP, Ruby, PerlMongoDB, Elasticsearch, DynamoDB
StructureAlong with classes and functions, variables can have varying structure and typesAlong with schemas and tables, documents can have varying structures and types
DeclarationWhile the program runsAs each document is written
EnforcementAt runtime, the moment of useYour code, when it reads the document back
Violation exampleCalling a method the object never gotReading 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.

Declarative data

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 languagesGraph databases
ExamplesHaskell, Lisp, Erlang, F#Neo4j, Dgraph, Amazon Neptune
StructureValues are combined through functions and expressions, with an emphasis on composition and immutable dataNodes are connected by explicitly stored edges that represent relationships
DeclarationBy describing transformations and the result they should produceBy describing the nodes, relationships, and paths that make up the desired result
EnforcementThe language runtime and its evaluation model determine how expressions are executedThe database engine determines how to traverse and optimize the graph
Violation exampleA function receives a value it cannot meaningfully transformA 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.

These are tendencies, not laws

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.