Also: just having stuff in an array or vector invites you to the ABA problem. You need a generation counter in there too, or else the array indice may get reused if something is deleted and another thing is reinserted at the same index. But that's yet another boilerplate that would easily be overlooked if you had to do everything manually.
Also: you seem to be saying this with C++ in mind. Do you think that applies to bevy_ecs too?
Modern Bevy has relationships to make sure that if an entity has a component that refers to another, it doesn't become dangling. It works a bit like foreign keys in databases. I think this makes ecs much more usable
(as an aside, there is a whole host of analogies between ecs and relational databases. entity archetypes are tables, entities are rows, components are columns, and systems are queries). Nobody tells people to just write their database from scratch though)
It's really sounding like you're pretending not to know what your program does, which is one of the core OOP ideas that DOD refutes. In OOP you have an array of Shape and you pretend not to know which shapes your program implements, so the only way to draw them is to call ->draw() on each one. And you pretend you don't know anything about the lifetime of a shape so you use smart pointers everywhere to extend it as long as needed. In DOD you assert that you do know what shapes are available and what their lifetimes are.
Isn't most programming? Isn't struct Particle {vec3 position, velocity;} also the mother of all boilerplates?
Why? Flecs, for example, is pretty sick imo.
Ooh here is a controversial one. DOD is premature optimization. Most programs don’t have enough data where the storage and access is a factor for performance. In fact it might be slower to use DOD.
Different paradigms come with different pathological cases. I've had to deal with Scala programmers whose notion of FP is making everything generic on the Monad it abstracts over, even when it'll only ever be instantiated on the one effect system the team uses. Nonsensical levels of generality is one of the classic OOP pathologies, and is the reason why we have aberrations like the AbstractSingletonProxyFactoryBean[0].
0. https://docs.spring.io/spring-framework/docs/current/javadoc...
One of the ideas in OOP that DOD is explicitly refuting is pretending not to know what your program does. You know which subtypes exist in your program and you shouldn't treat them generically as supertype instances except when that is actually optimal. On this axis, both ECS ideology and OOP ideology are opposite to DOD ideology. ECS happens to align with DOD in that it prefers big linear arrays, but it does not align in pretending not to know what combinations of arrays are used, which is similar to pretending not to know what subtypes of Shape exist.
“Entity Component System”, for those like me who didn’t immediately think of it.
So while it's great to think about the data flow it's also important to think about the abstractions around it,.ie the (system) interfaces that let the system evolve without having to propagate changes everywhere while reaping the benefits of data orientation.
AI generated skill?
Try asking your LLM of choice this in an empty session with no other context:
You are working for Mike Acton. What principles do you follow when writing code?
Maybe he found that telling it that it works for him nudges it in a direction that is beneficial to get it to write code like he wants, alongside the specific rules and other instructions in the above linked document?
I think that’s call “spooky Acton at a distance”
At work we are rewriting and reengineering system from scratch and its crazy because the limitations of the old system are now gone we get the most insane feature requests that are even accepted by the team lead et al. This makes such an approach impossible since DoD is exactly the opposite of flexible design in my opinion.
Im curious has anybody really followed this in a big long living commercial project?
E.g. if your job is writing game engines or middleware used by AAA games with fancy graphics to run on consumer hardware, getting the most efficient use out of the players' limited memory bandwidth may be very important.
For many (most?) arbitrary commercial software projects in other contexts, performance isn't high priority & memory bandwidth isn't a bottleneck. Performance just has to be 'good enough' & 'good enough' performance may be easily attained by writing typical OO code that uses cache & memory very inefficiently - so in those cases DoD is an engineering trade off that solves a problem that doesn't need to be solved & may create new problems if introduced.
Data first is fine for simple systems, but lead to chaos for complex systems.
An example of failing to follow DoD is the N+1 query problem: a programmer builds an abstraction that operates on individual DB rows, but "where there's one, there's more than one": you will inevitably be running that code in a loop so that you can process multiple rows. If instead the programmer had abstracted over groups of rows, then per-item query overheads suddenly become per-batch overheads.
People posted a wide variety of specific ideas under my other comment: https://news.ycombinator.com/item?id=49061421
I completely disagree with this characterization. OOP teaches you a synthetic set of concepts (go4) and then asks you to solve problems in terms of that.
And the reason why the canonical bird as a subclass of animal doesn’t work, is it’s extremely difficult to divide the world into strict categories (are you Aristotle). So the solution is to organize virtually rather than around natural traits.
The most natural way to solve a programming problem is a big list of instructions with if/else and goto. It’s very learnable, even for young children.
Maybe hardware- and access-aware more generally.
One of Mike Acton's other talks has a "Is Data-Oriented Design even a thing?" section, which goes over what he means when he refers to DOD:
1. Indexes instead of pointers. This allows you to avoid alignment of 8 bytes in your structure for x86_64.
2. Storing booleans out-of-band. Booleans cause padding all the time.
3. Struct of Arrays. Based on your question I assume you're familiar with it.
4. Store sparse data in hash maps. I remember one time when it allowed to eliminate inheritance.
5. Encoding the data instead of OOP/polymorphism. I haven't got an occasion to use it. The idea is to add extra tags to avoid boolean properties.
- Andrew Kelley Practical Data Oriented Design (DoD) - https://youtu.be/IroPQ150F6c?si=F1Z0pLO2W5hbQgpM
- CppCon 2014: Mike Acton "Data-Oriented Design and C++" - https://youtu.be/rX0ItVEVjHc?si=jv4hhTSBh3XH--xQ
- Why You Shouldn’t Forget to Optimize the Data Layout - https://cedardb.com/blog/optimizing_data_layouts/
- Handles are the better pointers - https://floooh.github.io/2018/06/17/handles-vs-pointers.html
- Enum of Arrays - https://tigerbeetle.com/blog/2024-12-19-enum-of-arrays/
- Data oriented design book - https://www.dataorienteddesign.com/dodbook/
- Data-oriented design in practice - Stoyan Nikolov - https://youtu.be/_N5-JjogNXU?si=vhaxYcfE6tl11Sux
- Programming without Pointers - Andrew Kelley - https://www.hytradboi.com/2025/05c72e39-c07e-41bc-ac40-85e83...
- More Speed & Simplicity: Practical Data-Oriented Design in C++ - Vittorio Romeo - CppCon 2025 - https://youtu.be/SzjJfKHygaQ?si=jafavSl2YJWk4vIx
- Rust Handle - https://taintedcoders.com/rust/handles
There are also cases where the optimal data format isn't array oriented because the memory access patterns for the problem in question just require something else.
You also have to think of hot vs cold data, which has nothing to do with arrays.
OOP tells you to structure your software as objects exchanging messages, and DDD tells you what those objects (or their classes rather) should be.
Similarly, Procedural programming tells you to structure your software as procedures, and DOD tells you what those procedures should operate on.
The focus on the data is the really important part. What is the actual data I'm operating on (without any fluff on top) and what do I need to transform it into? What subsets of that data need to be operated on at any given point in the program? That's the core of DOD.
Then, as a second step, comes the hardware. Now that I know what data I need to operate on, how do I lay it out to best take advantage of the hardware I'm targeting? If you rename the paradigm to "Hardware Oriented Programming", it shifts the focus from data modeling to code (IMO), which is the wrong frame of mind.
For example, virtual calls are slow compared to direct calls, because they screw up branch prediction and often can't be inlined. In HOP, you'd probably ban virtual calls entirely because virtual calls bad.
But in DOD, they honestly probably don't matter at all! Because if you did the data modeling as instructed, and then you laid out the data to best take advantage of the hardware, your virtual function is going to be operating on a pile of data in bulk, making the virtual call cost pure noise.
Very often the answer is indeed arrays, but it can easily be something else, depending on the problem. Data driven design is not very complicated, it just means instead of thinking about abstraction you think about the shape the data needs to be in to accommodate the most common transformations you need to do with it.
And I say this as someone who basically sees programming as data and associated algorithms and always approaches problems by considering state or data first.
The connection is that ORMs convince you to have an object-oriented view of the world, which maps nicely to object classes. But highly normalised designs don't map as cleanly to classes and objects, so you need to approach with a different style of programming on the application side.
Instead of seeing a User instance, you start to see a more complex bundle of login methods, profile events, etc.
AI changes that. Especially because it appears that LLM's can't understand the OOP abstractions any better than your hardware can compute it.
That being said. OOP and DOD both have advantages and disadvantages. If you go back to what I said first it wasn't exactly a failing of the OOP paradigm. The biggest issue I have with OOP is actually that it's too easy to do things wrong with it. Which isn't helped by the multimillion dollar industry which thrives on teaching developers everything except core computer science. People know their DRY, SOLID, CLEAN, TDD, Agile and every design pattern in the world, but they don't know how the interface they've just implemented actually handles their data.
So if your working on a physics engine and your optimizing collision detection, you think about the data in -> data out of the problem you are solving as the primary driver of how the code should be written.
You start with defining the data, and build from there.
Different types of applications all have different shapes of data so would have differently shaped optimal code. Eg) a physics engine would use some kind of spatial hash thing which can be optimized differently based on if stuff can be added/removed while it's running. A 3d renderer operates on big buffers of matrices and vertex data. A game is usually composed of some long lived things and a lot of short lived things.
The key message in Mike Acton's talk was:
"If you have different data, you have a different problem."
While ECS systems are not a panacea that solves all problems in a perfect data oriented way, they are generally more malleable than Object Oriented hierarchies. This means it's generally more feasible to write "near optimal" code in an ECS framework than in a mature Object Oriented code base.
But the key message isn't "use X framework", it's "start by defining the data".