Lumyst Logo

The Abstraction Gap

February 2, 2026Lumyst Team

There are two ways developers try to understand code. Both are broken.

Method 1: High-Level Architecture Diagrams

You open the docs. You see boxes and arrows. "Frontend talks to Backend. Backend talks to Database."

This tells you nothing useful.

You know the systems connect. But you don't know how. You don't know which functions get called. You don't know what order they run in. You don't know what conditions trigger different paths.

Example: The diagram shows "Payment Service → Fraud Service."

But it doesn't show:

  • Does the fraud check happen before or after charging the card?
  • What happens if the fraud check fails?
  • Does it refund automatically or require manual review?
  • Which function actually makes the API call?

High-level diagrams give you the structure. They don't give you the behavior. You can't verify logic with it. You can't understand what the code actually does.

Method 2: Reading Code Line-by-Line in Your IDE

You open the file. You start reading.

Here's what you see:

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { PaymentEntity } from './entities/payment.entity';
import { FraudService } from '../fraud/fraud.service';
import { NotificationService } from '../notification/notification.service';
import { Logger } from '@nestjs/common';
import { PaymentStatus } from './enums/payment-status.enum';
import { CreatePaymentDto } from './dto/create-payment.dto';

@Injectable()
export class PaymentService {
  private readonly logger = new Logger(PaymentService.name);

  constructor(
    @InjectRepository(PaymentEntity)
    private paymentRepository: Repository<PaymentEntity>,
    private fraudService: FraudService,
    private notificationService: NotificationService,
  ) {}

  async processPayment(dto: CreatePaymentDto): Promise<PaymentEntity> {
    this.logger.log(\`Processing payment for amount: ${dto.amount}\`);
    
    try {
      const payment = this.paymentRepository.create({
        amount: dto.amount,
        currency: dto.currency,
        status: PaymentStatus.PENDING,
      });

You're 20 lines in. You haven't learned anything yet.

What did you just read?

  • 8 import statements
  • Type definitions
  • Dependency injection setup
  • Logger initialization
  • Entity creation boilerplate

This is 80% of the code. None of it tells you what the payment flow actually does.

You need to keep reading. Another 30 lines. More validation. More error handling. More type conversions. Finally, buried in there, you find the actual logic:

const fraudCheck = await this.fraudService.verify(payment);
if (!fraudCheck.passed) {
  throw new FraudDetectedException();
}
await this.chargeCard(payment);

This is the business logic. This is what you actually needed to know. But you had to read 50 lines of noise to find 3 lines of signal.

The problem:

Code is written for machines. Machines need imports, types, decorators, error handling, logging. Humans don't need any of that to understand what the code does.

When you read code, you're doing manual translation work. You're converting syntax into meaning. You're filtering out the noise to find the intent.

This is slow. This is exhausting. This doesn't scale.

Example: Tracing a Refund Flow

You want to understand how refunds work. You start at the button click in the frontend.

Step 1: Find the event handler. It calls handleRefund(). Step 2: Click into handleRefund(). It calls the API client. Step 3: Click into the API client. It makes a POST request to /refunds. Step 4: Switch to the backend code. Find the refund controller. Step 5: Click into the controller. It calls RefundService.process(). Step 6: Click into RefundService. It calls PaymentGateway.refund(). Step 7: Click into PaymentGateway. It calls an external API. Step 8: Go back. Check if there's a webhook handler for the response. Step 9: Find the webhook controller. See what it does with the result.

You just clicked through 9 files. You opened 15 functions. You read 800 lines of code.

And here's the worst part: by the time you reach step 9, you've forgotten what happened in step 2. Your brain can only hold so much. You have to scroll back up. Re-read. Re-trace your path. Build the mental model again.

The code is forcing you to do two jobs:

  1. Navigation: Finding where the logic lives
  2. Translation: Converting syntax into meaning

Both jobs are hard. Doing them simultaneously is impossible at scale.

The Gap

High-level diagrams are too vague. They don't show you what actually happens.

Low-level code is too noisy. It buries the logic in syntax.

There's nothing in between.

What You Actually Need

You need to see the execution path without reading the implementation.

You need to see:

  • Which functions get called
  • In what order
  • Under what conditions
  • What each function's purpose is

You need the logic layer. Not the architecture. Not the syntax. The middle ground.

This is Call Trace

You click on a function. We show you the execution path as a graph.

Each node is a function. Edges show the call flow. Hover over a node and you see its intent. No imports. No types. No boilerplate. Just what it does.

Example: Instead of reading 50 lines of PaymentService code, you see:

processPayment()
├─ verify fraud
├─ charge card
└─ send notification

You understand the flow in 3 seconds. If you need to see the actual code for one of these steps, you click into it. But you don't have to read everything just to understand the path.

The Result

You stop translating syntax. You stop clicking through files. You see the behavior directly.

High-level diagrams don't help you debug. Low-level code drowns you in noise.

Call Trace gives you the middle layer. The one that actually maps to how you think about code.

The logic layer.

Experience the power of Call Trace.