DEV Community

Cover image for What I Learned After Building a Signal System from Scratch
Luciano0322
Luciano0322

Posted on

What I Learned After Building a Signal System from Scratch

Recap

At this point, this series on Signals and fine-grained reactivity is temporarily coming to an end.

This article will not introduce new technical details. Instead, I want to share some personal reflections and thoughts after writing the whole series.

The Influence of Ryan Carniato

In some sense, this entire series was inspired by Ryan Carniato, the creator of Solid.js.

Ryan has always been able to point directly at the pain points we face in React development. As a senior React engineer, I often find myself thinking:

Yes, this is exactly the problem I run into every day.

Solid.js is undoubtedly an excellent framework, although its learning curve can be high for beginners.

However, from my perspective, many of its design choices feel more reasonable than the direction React has taken. For example:

  • Effects without dependency arrays
  • Separating mounted logic from cleanup logic
  • JSX without the Virtual DOM

These are all things that many developers in the React community had asked for before, but they were never truly prioritized.

The Dilemma of React and Next.js

Over the past few years, React’s direction has become increasingly influenced by the needs of the Next.js ecosystem.

Server Components introduced an important architectural shift, but they also forced many frontend engineers to adopt a model that still feels immature in everyday application development.

To be clear, I am not saying Server Components are useless. They solve real problems around server-side data access, streaming, and reducing client-side JavaScript. But from a frontend developer’s perspective, they also make the mental model of React significantly more complex.

To be honest, I sometimes miss the era of “Next.js without Server Components.” React was simpler back then. Its problems were obvious, but at least the boundaries were easier to reason about, and we had more freedom to choose our own solutions.

People in the community have proposed ideas such as “Should React introduce signals?” or even “Should React explore a version without the Virtual DOM?” These ideas may not align naturally with the current product direction and incentives around React and Next.js, but I still think they are worth discussing seriously.

My point is not that React is wrong or that the Virtual DOM is useless.

The Virtual DOM is still a reasonable abstraction in many scenarios. For example, in cross-platform environments such as React Native, it provides a practical compromise between a declarative UI model and multiple rendering targets.

My point is narrower:

The Virtual DOM is not wrong. It is just no longer the only reasonable center of frontend architecture.

When the problem is fine-grained data dependency, update precision, and scheduler correctness, signals provide a different model that deserves serious consideration.

What I see today is a React ecosystem entering a more conflicted phase. Although projects like TanStack are still doing excellent work to improve the developer experience, if a mature alternative appears in the future, I may no longer choose Next.js by default.

Common Misunderstandings

“Isn’t this just Jotai?”

In the community, you may sometimes hear people say that Jotai — or the atomic state model in general — is basically the same thing as signals. Some even argue that because it is built closer to the framework ecosystem, it is more developer-friendly.

But if you have followed this series and implemented the concepts step by step, or if you have ever implemented a reactive system yourself, you will understand that the two approaches are based on very different design philosophies.

The key difference is whether the system handles dependencies.

Below is a simple conceptual comparison.

Atomic State: The Recoil / Jotai Family

  • Core idea: Split state into the smallest possible units, often called state atoms. There is no implicit dependency tracking between them.
  • Composition model: Use selectors or derived functions to organize logic and compose state.
  • Update model: When state changes, the system does not automatically notify downstream dependents. Instead, selectors are re-executed as pure functions.
  • Characteristics:

    • Easy to understand in a functional way: state is a value, and a selector is a pure function.
    • However, the dependency graph is not explicit. Every read may require recomputation, which can lead to repeated calculations.

Signals

  • Core idea: Signals are also fine-grained state units, but they include an explicit dependency-tracking graph.
  • Composition model: computed and effect automatically register dependencies during execution. When a source value changes later, downstream subscribers are notified automatically.
  • Update model: Usually push-based. Once a source value changes, downstream nodes are marked as stale, and the scheduler decides when to run updates.
  • Characteristics:

    • Avoids unnecessary recomputation because the runtime knows who depends on whom.
    • Update propagation is faster and more precise.
    • Requires the runtime to manage a dependency graph.

Summary of the Difference

In one sentence:

  • Atomic state: Splits state, but does not deeply manage dependencies. The system needs to “calculate again” through functions.
  • Signals: Splits state and adds dependency tracking. The system knows who needs to update.

Not handling dependencies does make the model easier to write and understand. Jotai also relies on the framework’s own state model, which naturally aligns with the framework lifecycle and avoids many synchronization problems.

But the trade-off is that this model must rely on framework re-renders to guarantee correctness. Signals, on the other hand, can achieve more fine-grained updates through runtime-level dependency tracking.

Flow Difference

Atomic state vs signal flow

“Isn’t this just Dependency Injection?”

Another common comparison is to treat signals, especially when integrated with Vue or React, as something similar to Dependency Injection (DI) in the traditional OOP world.

At first glance, it does look somewhat similar: you inject external state into a framework and use it inside the application.

But the essential difference is this:

  • DI solves the composition and management of object dependencies.
  • Signals solve the tracking and update timing of data dependencies.

They may look similar on the surface, but they solve problems at completely different dimensions.

What Does DI Solve?

  • It answers: “Which object needs which service?”
  • Timing: Construction time
  • Keywords: dependency relationship, object composition
  • Example: A Car needs an Engine, and the DI framework injects it for you.
class Engine {}

class Car {
  constructor(private engine: Engine) {}
}
Enter fullscreen mode Exit fullscreen mode

What Do Signals Solve?

  • They answer: “Which piece of data depends on which other piece of data?”
  • Timing: Runtime
  • Keywords: data tracking, state update propagation
  • Example: totalPrice depends on count * unitPrice, and it updates automatically when the source values change.
const count = signal(2);
const unitPrice = signal(100);
const totalPrice = computed(() => count.get() * unitPrice.get());
Enter fullscreen mode Exit fullscreen mode

A Simple Clarification

  • DI helps objects get the services they need.
  • Signals help the system know who should update when data changes.

“Signals are just another state management tool”

Many people think signals are just another form of state management.

But after going through the implementation details in this series, you can see that signals touch much more than state management.

A state management library usually helps you organize where state lives and how it is updated.

A signal system goes one layer deeper. It asks:

  • How are data dependencies tracked?
  • How are invalidations propagated?
  • When should derived values recompute?
  • When should effects run?
  • How should the scheduler preserve consistency?

This is why reducing signals to “just another state management tool” misses the point.

What signals challenge is not merely state management, but the long-standing assumption that the Virtual DOM must sit at the center of frontend architecture.

That does not mean the Virtual DOM has no value. It means we should treat it as one architectural trade-off among many, not as the only valid mental model for UI development.

Technical evolution is never just about code. It is also about incentives, existing knowledge systems, and the cost of changing how people think.

Signals are uncomfortable because they move the discussion away from component re-rendering and toward data dependency itself.

Adjusting Your Mindset: Facing Irrational Criticism

Some people will directly deny the value of signals. Some may even mock them with comments like:

Signals are useless.

Dan already told you not to use them.

These comments often carry more emotion or tribal position than technical understanding.

To me, these voices are not a threat. They are more like a mirror:

  • If you already understand the difference between atomic state and signals, you can easily see the blind spots in the argument.
  • If you do not understand it yet, then it is a useful reminder that you should study the topic more deeply.

Of course, this does not mean we should blindly reject React’s design decisions either. React has solved many real engineering problems, and its ecosystem is still extremely valuable.

But no framework author, no matter how experienced or influential, should become a reason for us to stop thinking.

The healthier attitude is to return to concrete examples, implementation details, and trade-offs.

So when you encounter this kind of criticism, the best response is not to rush into an argument.

Return to the code. Return to the design principles.

Once you can explain the difference in dependency tracking with a simple example, the mockery naturally loses its power.

The Future of Signals

Overall, I am quite optimistic about the future of signals.

The TC39 Signals proposal from last year already shows that signals are becoming increasingly important in frontend development.

Apart from React, which still seems to maintain a “React knows best” atmosphere, most mainstream frameworks have already entered a new era with signal-like ideas:

  • Vue’s Vapor Mode
  • Angular 17+ Signals
  • Svelte Runes
  • Preact Signals
  • SolidJS

Some of them are even moving toward removing the unnecessary parts of the Virtual DOM, such as Vue’s Vapor Mode and SolidJS’s JSX compilation model.

This means that developers who truly understand the core ideas behind signals will be much more comfortable in future frontend applications.

In my own experience, once I understood these concepts, switching between different frameworks became much easier.

Why I Started This Series

The reason I started this series was that I was building my own open-source signal system library: signal-kernel.

During that process, I kept running into challenges. Then I realized something important:

Many of the “algorithms and data structures” we learned in school or practiced for interviews are actually useful here.

This made me even more convinced of one thing:

If you want to design the foundation of a framework well, you must understand JavaScript fundamentals and computer science.

What I Learned

To me, this series is not just a set of technical notes. It is also part of my own journey as an engineer.

  • It started from my frustration with React.
  • It was inspired by Ryan Carniato’s ideas.
  • It eventually led me back to the importance of JavaScript fundamentals and algorithms.

The biggest lesson I learned is this:

Frameworks will come and go, but fundamental knowledge and mental models are what allow you to go further.

This series only peels back the first layer of a framework engine: the reactive core.

Of course, the reactive core is also one of the most important foundations of any framework. If we continue going deeper, we will run into topics such as:

  • DOM rendering
  • UI rendering schedulers
  • Component models

Every framework makes different decisions and trade-offs in these areas.

These may become directions for my future research.

Advice for Readers

If you are currently learning React, you do not need to rush into Solid.js or any other framework.

The truly important thing is not which framework you choose. The important thing is understanding the underlying concepts:

  • The data flow from state → derivation → effect
  • Why schedulers and dependency graph management matter
  • The difference between push and pull
  • The difference between eager and lazy execution

Once you understand these ideas, you can quickly find your direction in any framework.

Do not just learn how to use a framework. Learn the logic underneath it.

Trends will change, but fundamental concepts are the real assets that engineers can carry with them.

Signal Implementation Reference

As usual, I will also attach the implementation code from this series for reference:

reactivity_lessons

The repository includes unit tests for each section, although only the parts that changed in each chapter are tested.

I still hope readers will modify the examples themselves instead of simply copying and pasting the code.

If you only copy the implementation directly, you will miss many opportunities to think through the details.

The examples I provide are intentionally designed choices. In many cases, the data structures and subscription model may not fit every development scenario. However, they make the concepts easier for beginners to understand.

I did not want to switch between too many algorithms and data structures during the explanation, because that would introduce unnecessary confusion.

For more advanced discussion, I recommend focusing on Ryan Carniato’s article series, especially Derivations in Reactivity, which explains many of the trade-offs behind today’s mainstream signal implementations.

Top comments (0)