Back to all articles
From Physics to Pixel: How to Understand End-to-End Systems in 2025

From Physics to Pixel: How to Understand End-to-End Systems in 2025

Why do most engineers fail to solve real problems? Because they only see one layer of reality. Learn how to master systems from hardware to interface.

Human-architected research synthesized with the assistance of AI personas.
14 min read

TL;DR / Executive Summary

Why do most engineers fail to solve real problems? Because they only see one layer of reality. Learn how to master systems from hardware to interface.

Why do most engineers fail to solve real problems? Because they only see one layer of reality.

💡 TL;DR (Too Long; Didn't Read)

  1. Abstractions are an illusion: Blindly trusting frameworks without understanding lower layers (hardware, network, OS) is the recipe for complex, unsolvable failures.
  2. Systems are a continuum: UI problems can have causes in physics (electromagnetic interference) and vice versa. An elite engineer knows how to debug at all layers.
  3. Master the complete flow: The ability to trace a problem from electrical signal (Layer 1) to user interface (Layer 9) is the skill that will make you irreplaceable in an age of automation and AI.
  4. Practical action: Choose a system you use and try to map its complete operation, from physics to pixel. This is the first step to becoming an end-to-end engineer.

The Moment Everything Changed

It was 1987. I was debugging a telecommunications terminal that simply refused to establish reliable connections. The code was perfect. The protocols, correctly implemented. But every 47 minutes, exactly, the system would crash.

Forty-eight hours later, I discovered: electromagnetic interference from an industrial air conditioning motor located three floors below. The cable wasn't adequately shielded, and the voltage oscillation generated by the compressor induced enough noise to corrupt exactly one critical bit in the UART control register.

At that moment, I understood a fundamental truth that would shape four decades of career: systems don't exist in isolated layers. They exist in a continuum that ranges from quantum physics to user experience.

The Illusion of Perfect Abstractions

Today, I observe an entire generation of engineers who blindly believe in abstractions. "I don't need to understand how it works underneath," they say. "The framework solves that for me."

This is the biggest lie in modern engineering.

When your React app mysteriously crashes only on iPhone 14 Pro Max, when connected to specific 5G networks, during peak hours, you'll need to descend all layers of the stack to find the answer. And it could be anywhere:

  • In physics: Interference between 5G antennas and internal components
  • In hardware: A16 Bionic processor thermal throttling
  • In operating system: iOS memory management
  • In network: TCP protocol congestion on 5G cells
  • In framework: Race condition in React Concurrent Mode
  • In interface: Excessive reflow caused by CSS animations

End-to-end engineers not only know where to look – they know how each layer influences the others.


The Anatomy of a Real System: From Atom to Avatar

Let me show you how a real system works, using an example I lived: a contactless payment terminal in a São Paulo subway.

Layer 1: Quantum Physics (Yes, It Matters)

When you bring your card close to the NFC reader, you're creating an inductive coupling between two coils. The physics behind it is elegant:

Magnetic Field (H) = (N × I) / (2π × r)

Where:

  • N = number of antenna turns
  • I = alternating current (13.56 MHz)
  • r = distance between coils

Why does this matter for a developer? Because when your mobile app fails to process NFC payments "randomly," you need to understand that:

  1. Distance: Above 4cm, the field doesn't have enough energy
  2. Orientation: Perpendicular coils = zero coupling
  3. Interference: Other devices on the same frequency cause collisions
  4. Material: Metallic cases completely block the field

Layer 2: Electronics and Digital Signals

The magnetic field induces a current in the card, which powers the microprocessor. Here, digital electronics comes in:

c
// NFC signal demodulation in embedded C uint8_t demodulate_ask(uint16_t adc_sample) { static uint16_t envelope_avg = 0; static uint8_t bit_phase = 0; // Envelope detection for ASK demodulation uint16_t envelope = abs(adc_sample); envelope_avg = (envelope_avg * 7 + envelope) >> 3; // Moving average filter // Bit decision based on envelope return (envelope > envelope_avg * 1.2) ? 1 : 0; }

Critical insight: Each bit transmitted via NFC goes through analog-to-digital conversion, demodulation, error correction, and decoding. Understanding this explains why readings fail in environments with lots of electromagnetic noise.

Layer 3: Communication Protocols

Now we have bits. But bits aren't data. We need protocols. In the case of NFC, we follow ISO 14443:

NFC Frame:
[SOF] [Length] [Data...] [CRC16] [EOF]
 1bit   8bits   N*8bits   16bits  1bit

The protocol implements:

  • Collision detection: Multiple cards in the same area
  • Anti-collision: Algorithm to select a specific card
  • Error correction: CRC16 to validate integrity
  • Flow control: ACK/NACK to confirm reception
c
// RFID/NFC anti-collision implementation typedef struct { uint8_t sak; // Select acknowledge uint8_t uid[10]; // Unique identifier uint8_t uid_len; } nfc_card_t; int nfc_anticollision(nfc_card_t* cards, int max_cards) { int found = 0; uint8_t collision_level = 0; while (found < max_cards && collision_level < 32) { uint8_t select_cmd[] = {0x93, 0x20}; // SEL + NVB uint8_t response[5]; if (send_command(select_cmd, 2, response, 5)) { // Check collisions in received UID if (check_collision(response)) { collision_level++; continue; } cards[found].uid_len = 4; memcpy(cards[found].uid, response, 4); found++; } collision_level++; } return found; }

Layer 4: Cryptographic Security

Data transmitted, but not necessarily secure. Enter EMV and cryptography:

c
// DUKPT implementation (Derived Unique Key Per Transaction) void derive_transaction_key(uint8_t* base_key, uint32_t transaction_counter, uint8_t* derived_key) { uint8_t counter_bytes[8]; uint32_to_bytes(transaction_counter, counter_bytes); // XOR operation with base key for (int i = 0; i < 8; i++) { derived_key[i] = base_key[i] ^ counter_bytes[i]; } // Triple DES for final derivation tdes_encrypt(derived_key, 8, base_key + 8, derived_key); } // Transaction encryption void encrypt_transaction(transaction_t* txn) { uint8_t session_key[16]; derive_transaction_key(terminal_master_key, txn->counter, session_key); // Encrypt PAN + Amount + Date uint8_t plain_data[32]; serialize_transaction(txn, plain_data); tdes_encrypt(plain_data, 32, session_key, txn->encrypted_data); }

Why it matters: Each transaction uses a unique cryptographic key, mathematically derived. If you don't understand this layer, you can't debug transaction rejection problems from the issuing bank.

Layer 5: Embedded Operating System

Our terminal runs embedded Linux. Here, operating systems knowledge becomes crucial:

c
// NFC device driver for Linux #include <linux/module.h> #include <linux/interrupt.h> #include <linux/spi/spi.h> static irqreturn_t nfc_interrupt_handler(int irq, void *dev_id) { struct nfc_device *nfc_dev = dev_id; // Read status register via SPI u8 status = nfc_read_register(nfc_dev, NFC_STATUS_REG); if (status & NFC_RX_COMPLETE) { // Signal userspace thread via wait queue wake_up_interruptible(&nfc_dev->read_queue); } return IRQ_HANDLED; } // GPIO interrupt configuration for NFC field detection static int nfc_probe(struct spi_device *spi) { int ret = request_irq(gpio_to_irq(NFC_IRQ_GPIO), nfc_interrupt_handler, IRQF_TRIGGER_FALLING, "nfc-reader", nfc_dev); return ret; }

Critical insight: System performance depends on how interrupts are handled. Excessive ISR latency can cause NFC data loss.

Layer 6: Android Application

The terminal runs an Android application that processes the transaction:

kotlin
class PaymentProcessor @Inject constructor( private val nfcManager: NfcManager, private val cryptoService: CryptoService, private val networkService: NetworkService ) { suspend fun processPayment(amount: BigDecimal): PaymentResult { return withContext(Dispatchers.IO) { try { // 1. Read card data via NFC val cardData = nfcManager.readCard().await() // 2. Validate EMV locally val emvResult = validateEmvData(cardData) if (!emvResult.isValid) { return@withContext PaymentResult.Declined("Invalid EMV") } // 3. Create ISO 8583 message val isoMessage = createIsoMessage(cardData, amount) // 4. Send to authorizer val response = networkService.authorize(isoMessage).await() // 5. Process response parseAuthorizationResponse(response) } catch (e: Exception) { PaymentResult.Error(e.message ?: "Unknown error") } } } }

Layer 7: Network and Protocols

The transaction needs to reach the bank. Here comes TCP/IP, TLS, and cellular networks:

kotlin
class NetworkService { private val client = OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) .addInterceptor { chain -> val request = chain.request().newBuilder() .addHeader("Content-Type", "application/json") .addHeader("X-Terminal-ID", getTerminalId()) .addHeader("X-Transaction-Key", generateTransactionKey()) .build() chain.proceed(request) } .build() suspend fun authorize(isoMessage: String): String = suspendCancellableCoroutine { continuation -> val request = Request.Builder() .url("https://acquirer.bank.com/api/v2/authorize") .post(isoMessage.toRequestBody()) .build() client.newCall(request).enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { if (response.isSuccessful) { continuation.resume(response.body?.string() ?: "") } else { continuation.resumeWithException(HttpException(response.code)) } } override fun onFailure(call: Call, e: IOException) { continuation.resumeWithException(e) } }) } }

Important detail: Network timeout must consider cellular network latency (can reach 2-3 seconds on congested 4G).

Layer 8: Backend and Processing

On the acquirer's server, we have scaled processing:

go
// Transaction processor in Go type TransactionProcessor struct { db *sql.DB cryptoHSM *hsm.Client rateLimit *rate.Limiter circuitBreaker *breaker.CircuitBreaker } func (tp *TransactionProcessor) ProcessAuthorization(ctx context.Context, req *AuthRequest) (*AuthResponse, error) { // Rate limiting per terminal if !tp.rateLimit.Allow() { return nil, errors.New("rate limit exceeded") } // Circuit breaker for network failures result, err := tp.circuitBreaker.Execute(func() (interface{}, error) { return tp.authorizeWithBank(ctx, req) }) if err != nil { return nil, err } auth := result.(*AuthResponse) // Persist transaction if err := tp.storeTransaction(ctx, req, auth); err != nil { log.Error("Failed to store transaction", err) // Don't fail transaction due to storage problem } return auth, nil } func (tp *TransactionProcessor) authorizeWithBank(ctx context.Context, req *AuthRequest) (*AuthResponse, error) { // Decryption using HSM decrypted, err := tp.cryptoHSM.Decrypt(req.EncryptedData) if err != nil { return nil, fmt.Errorf("decryption failed: %w", err) } // Parse ISO 8583 isoMsg, err := iso8583.Parse(decrypted) if err != nil { return nil, fmt.Errorf("invalid ISO message: %w", err) } // Business validations if err := tp.validateTransaction(isoMsg); err != nil { return &AuthResponse{ResponseCode: "05"}, nil // Do not honor } // Call issuing bank return tp.callIssuerBank(ctx, isoMsg) }

Layer 9: User Interface

Finally, the result appears on screen:

kotlin
@Composable fun PaymentScreen( amount: BigDecimal, onPaymentComplete: (PaymentResult) -> Unit ) { var paymentState by remember { mutableStateOf(PaymentState.Idle) } var progress by remember { mutableStateOf(0f) } LaunchedEffect(paymentState) { when (paymentState) { PaymentState.Processing -> { // Realistic progress animation val steps = listOf( "Reading card..." to 0.2f, "Validating data..." to 0.4f, "Connecting to bank..." to 0.6f, "Processing..." to 0.8f, "Finishing..." to 1.0f ) steps.forEach { (message, targetProgress) -> delay(800) // Realistic time for each step progress = targetProgress } } } } Column( modifier = Modifier .fillMaxSize() .padding(24.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { when (paymentState) { PaymentState.Idle -> { NfcIcon() Text("Bring card closer", style = MaterialTheme.typography.h5) Text("R$ ${amount.formatCurrency()}", style = MaterialTheme.typography.h3) } PaymentState.Processing -> { CircularProgressIndicator(progress = progress) Text("Processing payment...", style = MaterialTheme.typography.body1) } is PaymentState.Success -> { Icon(Icons.Default.CheckCircle, tint = Color.Green) Text("Payment approved!", style = MaterialTheme.typography.h5) } is PaymentState.Error -> { Icon(Icons.Default.Error, tint = Color.Red) Text("Payment denied", style = MaterialTheme.typography.h5) Text(paymentState.message, style = MaterialTheme.typography.body2) } } } }

The Invisible Failure Points

Here's what makes an end-to-end engineer irreplaceable: we know where systems break and why.

Failure 1: The Phantom Timeout

Symptom: Transactions fail randomly with timeout Apparent layer: Network Real cause: Garbage collection on Android pausing network thread for 2+ seconds Solution: Configure incremental GC and use background threads for critical operations

Failure 2: The Mysterious Interference

Symptom: NFC stops working in certain locations Apparent layer: Hardware Real cause: 13.56MHz frequency conflicting with nearby industrial heaters Solution: More restrictive band-pass filter and additional shielding

Failure 3: The Duplicate Transaction

Symptom: Users being charged twice Apparent layer: Backend Real cause: Race condition between UI thread and background service on Android Solution: Idempotency keys and debouncing on frontend

Failure 4: The Subtle Crash

Symptom: Terminal crashes after exactly 4 hours Apparent layer: Software Real cause: 16-bit counter overflow (65536 transactions / 16 transactions per hour = 4096 hours... oops, 4096 seconds = 68 minutes... calculation error) Solution: 32 or 64-bit counters for values that can grow


The Vertical Debugging Methodology

When a system fails, use this methodology I developed over 40 years:

1. Reproduce the Problem Deterministically

bash
# Script to reproduce specific conditions #!/bin/bash # Simulate unstable network conditions sudo tc qdisc add dev eth0 root netem delay 100ms loss 1% # Stress CPU stress --cpu 4 --timeout 60s & # Run test ./run_payment_test.sh --transactions=100

2. Collect Data from All Layers

bash
# Simultaneous log collection tail -f /var/log/syslog & # System logcat | grep "PaymentApp" & # Android tcpdump -i any -w network.pcap & # Network dmesg -w | grep -i error & # Kernel

3. Correlate Temporally

python
import pandas as pd from datetime import datetime # Temporal correlation analysis between logs def correlate_logs(system_log, app_log, network_log): # Parse timestamps to common format system_events = parse_system_log(system_log) app_events = parse_app_log(app_log) network_events = parse_network_log(network_log) # Merge by timestamp with ±100ms window correlation_window = pd.Timedelta(milliseconds=100) for app_event in app_events: nearby_system = system_events[ (system_events['timestamp'] >= app_event['timestamp'] - correlation_window) & (system_events['timestamp'] <= app_event['timestamp'] + correlation_window) ] if len(nearby_system) > 0: print(f"Correlation found: {app_event} -> {nearby_system}")

4. Test Hypotheses in Isolated Layers

c
// Isolated NFC layer test void test_nfc_layer_isolated() { // Simulate perfect data from upper layers uint8_t perfect_emv_data[] = {0x9F, 0x02, 0x06, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00}; // Amount uint8_t perfect_pan[] = {0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42}; // Test PAN // Test only NFC communication nfc_result_t result = nfc_transceive(perfect_emv_data, sizeof(perfect_emv_data), response, &response_len); if (result != NFC_SUCCESS) { printf("NFC layer isolated failure: %d\n", result); // Now we know the problem is in NFC layer, not above } }

The Future of End-to-End Systems

Edge Computing and 5G

The next revolution is coming. With 5G and edge computing, we'll have:

go
// Distributed processing on edge nodes type EdgeProcessor struct { localCache *cache.Redis aiInference *tflite.Interpreter // TensorFlow Lite for edge cloudFallback *http.Client } func (ep *EdgeProcessor) ProcessPayment(ctx context.Context, txn *Transaction) (*Result, error) { // 1. Try local processing with AI if riskScore := ep.calculateRiskLocally(txn); riskScore < 0.3 { return ep.approveLocally(txn), nil } // 2. For suspicious transactions, go to cloud return ep.cloudFallback.Process(ctx, txn) } func (ep *EdgeProcessor) calculateRiskLocally(txn *Transaction) float32 { // TinyML model running on edge input := []float32{ float32(txn.Amount), float32(txn.Merchant.RiskCategory), float32(time.Now().Hour()), // Hour of day // ... other features } ep.aiInference.SetInputTensor(0, input) ep.aiInference.Invoke() output := ep.aiInference.GetOutputTensor(0) return output.Float32s()[0] // Risk score between 0-1 }

Quantum-Safe Cryptography

Quantum computers are coming. We need to prepare:

c
// Post-quantum cryptography implementation #include <oqs/oqs.h> typedef struct { OQS_SIG* signature_scheme; uint8_t* public_key; uint8_t* private_key; } quantum_safe_keys_t; int generate_quantum_safe_keys(quantum_safe_keys_t* keys) { keys->signature_scheme = OQS_SIG_new(OQS_SIG_alg_dilithium_3); if (!keys->signature_scheme) return -1; keys->public_key = malloc(keys->signature_scheme->length_public_key); keys->private_key = malloc(keys->signature_scheme->length_secret_key); return OQS_SIG_keypair(keys->signature_scheme, keys->public_key, keys->private_key); }

Embedded AI (TinyML)

Smart processing directly on the terminal:

c
// Fraud detection using TinyML #include "tensorflow/lite/micro/all_ops_resolver.h" #include "tensorflow/lite/micro/micro_interpreter.h" class FraudDetector { private: tflite::MicroInterpreter* interpreter; const tflite::Model* model; public: bool initialize(const uint8_t* model_data) { model = tflite::GetModel(model_data); static tflite::AllOpsResolver resolver; static tflite::MicroInterpreter static_interpreter( model, resolver, tensor_arena, kTensorArenaSize); interpreter = &static_interpreter; return interpreter->AllocateTensors() == kTfLiteOk; } float calculateFraudProbability(const TransactionData& txn) { // Prepare input tensor float* input = interpreter->input(0)->data.f; input[0] = txn.amount / 1000.0f; // Normalized amount input[1] = txn.hour_of_day / 24.0f; // Time feature input[2] = txn.merchant_category / 100.0f; // Category feature // Execute inference interpreter->Invoke(); // Return fraud probability return interpreter->output(0)->data.f[0]; } };

The Skill That Makes You Irreplaceable

In a world where AI can generate code and frameworks abstract complexity, there's one skill that no automation can replace: the ability to see the system as a whole and understand how each layer influences the others.

When the system fails – and all systems fail – you'll be the person called to solve it. Because while others look for the solution in a specific layer, you see the interaction between all of them.

This is the difference between being a coder and being an engineer. Between following tutorials and solving real problems. Between being replaceable and being indispensable.

Your Next Action

Take a system you use daily. It could be your banking app, your car's GPS, or even an e-commerce checkout.

Trace the complete path: from the screen touch to the database, through all intermediate layers. Draw each component. Identify possible failure points.

Now you're thinking like an end-to-end engineer.

And when you can visualize this complete path, you'll understand why some engineers are worth 10x more than others.


This is just the first article in a series about real systems engineering. In the next one, I'll show you how 40 years of experience taught me lessons that AI hasn't discovered yet.

Want to receive the next articles and real debugging cases? Subscribe to my newsletter for engineers who want to go beyond abstractions.

Receive new articles

Subscribe to receive notifications about new articles directly to your email

We won't send spam. You can unsubscribe at any time.