BYU Strategy - Marriott School of Business

Sustainable Code

Why This Matters

Your product is live. Users are signing up. Features are shipping. Everything is working, for now.

But under the hood, you’ve been making tradeoffs. That function you copy-pasted because there wasn’t time to refactor. The API route that handles five different things. The component that’s 800 lines long. The tests you meant to write.

This is technical debt, and every product accumulates it. This chapter teaches you to recognize tech debt, decide when to pay it down, and implement practices that keep your codebase sustainable as you scale.

Part 1: The Strategy

What is Technical Debt?

Technical debt is the implicit cost of additional rework caused by choosing an expedient solution now instead of using a better approach that would take longer.

The metaphor comes from finance: just like financial debt, technical debt: - Accumulates interest: The longer you wait to pay it down, the more expensive it becomes - Can be strategic: Sometimes borrowing makes sense - Can be crippling: Too much debt slows everything down

Examples of technical debt: - Duplicated code across multiple files - Hard-coded values that should be configurable - Missing tests for critical functionality - Components that do too many things - Outdated dependencies with security vulnerabilities - No error handling for edge cases

Strategic vs. Accidental Debt

Not all tech debt is bad. Some is intentional and strategic.

Strategic (Deliberate) Debt

When it makes sense: - Racing to validate product-market fit - Meeting a hard deadline (demo, investor pitch) - Testing a hypothesis quickly - Competitive pressure requires speed

Example: > “We know this data model won’t scale past 10,000 users, but we need to launch next week to test demand. If it works, we’ll rebuild the database layer.”

The key: You know you’re taking on debt, and you have a plan to address it.

Accidental (Inadvertent) Debt

How it happens: - Developers didn’t know a better approach - Requirements changed after implementation - Quick fixes that became permanent - Poor communication about system design

Example: > “I didn’t realize there was already a date formatting utility, so I wrote another one. Now we have three different date formatters and they don’t all produce the same output.”

The Tech Debt Quadrant

Reckless Prudent
Deliberate “We don’t have time for tests” “Ship now, refactor later”
Inadvertent “What’s a design pattern?” “Now we know how we should have built it”

Prudent deliberate debt is often justified. It’s a conscious business decision.

Reckless inadvertent debt is the most dangerous, you don’t even know you’re accumulating it.

When to Pay Down Tech Debt

The answer isn’t “always” or “never”. It depends on context.

Pay it down when: - The debt is blocking new feature development - You’re about to modify code in that area anyway - It’s causing bugs or performance issues - Onboarding new developers is difficult - Security vulnerabilities are involved

Defer payment when: - You’re still validating product-market fit - The code might be deleted soon (pivoting) - The cost of refactoring exceeds the benefit - You have hard deadlines with real consequences

The Boy Scout Rule: Leave the code better than you found it. When you’re working in an area, clean up a little. Incremental improvements compound over time.

Growth and Retention Strategy

As your product matures, focus shifts from acquisition to retention. Sustainable code enables sustainable growth.

The User Lifecycle

Awareness → Acquisition → Activation → Retention → Revenue → Referral
                            ↑
                       You are here
                    (Sprint 3-4 focus)

By this point, you should have: - Acquisition: Users can find and sign up - Activation: Users experience core value

Now focus on: - Retention: Users keep coming back - Revenue: Users are willing to pay - Referral: Users tell others

Retention Levers

Lever Description Technical Implementation
Habit formation Make the app part of daily routine Push notifications, email reminders
Increased value More usage = more value Data accumulation, personalization
Social connection Users connect with others Sharing, collaboration features
Switching costs Leaving means losing something Data export friction (ethical limits)

Onboarding Optimization

The first experience determines whether users return.

Good onboarding: - Gets users to the “aha moment” fast - Teaches by doing, not explaining - Celebrates small wins - Removes friction ruthlessly

Common mistakes: - Too many steps before value - Requiring unnecessary information - Information overload - No guidance after signup

Measure it: - Time to first value action - Completion rate of onboarding steps - Drop-off points in the flow - Day 1 retention vs. users who completed onboarding

Part 2: Building It

Code Quality Fundamentals

The DRY Principle

DRY (Don’t Repeat Yourself) means each piece of knowledge should exist in exactly one place.

Before (repetitive):

// Three places with the same logic
function validateStudentDiscount(user) {
  if (!user.email) return false;
  return user.email.endsWith('.edu');
}

function calculateStudentPrice(price, user) {
  if (!user.email) return price;
  if (user.email.endsWith('.edu')) {
    return price * 0.8;
  }
  return price;
}

function showStudentBanner(user) {
  if (user.email && user.email.endsWith('.edu')) {
    return true;
  }
  return false;
}

After (DRY):

// One source of truth
function isStudent(user) {
  return user.email?.endsWith('.edu') ?? false;
}

function validateStudentDiscount(user) {
  return isStudent(user);
}

function calculateStudentPrice(price, user) {
  return isStudent(user) ? price * 0.8 : price;
}

function showStudentBanner(user) {
  return isStudent(user);
}

Single Responsibility

Each function, component, or module should do one thing.

Before (doing too much):

function handleUserSubmit(formData) {
  // Validate
  if (!formData.email) throw new Error('Email required');
  if (!formData.name) throw new Error('Name required');

  // Transform
  const user = {
    email: formData.email.toLowerCase(),
    name: formData.name.trim(),
    createdAt: new Date()
  };

  // Save
  await supabase.from('users').insert(user);

  // Send email
  await sendWelcomeEmail(user.email);

  // Track
  await trackEvent('user_signed_up', { email: user.email });

  // Redirect
  router.push('/dashboard');
}

After (single responsibility):

async function handleUserSubmit(formData) {
  const validatedData = validateUserForm(formData);
  const user = await createUser(validatedData);
  await onUserCreated(user);
  router.push('/dashboard');
}

function validateUserForm(formData) {
  if (!formData.email) throw new Error('Email required');
  if (!formData.name) throw new Error('Name required');
  return formData;
}

async function createUser(formData) {
  const user = transformUserData(formData);
  await supabase.from('users').insert(user);
  return user;
}

async function onUserCreated(user) {
  await Promise.all([
    sendWelcomeEmail(user.email),
    trackEvent('user_signed_up', { email: user.email })
  ]);
}

Linting and Formatting

Automated tools catch problems and maintain consistency without manual effort.

ESLint Setup

npm install -D eslint @eslint/js
npx eslint --init

Basic .eslintrc.json:

{
  "extends": ["next/core-web-vitals", "eslint:recommended"],
  "rules": {
    "no-unused-vars": "warn",
    "no-console": "warn",
    "prefer-const": "error"
  }
}

Prettier Setup

npm install -D prettier eslint-config-prettier

Create .prettierrc:

{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5"
}

Add to package.json:

{
  "scripts": {
    "lint": "eslint .",
    "format": "prettier --write .",
    "format:check": "prettier --check ."
  }
}

Testing Fundamentals

Tests catch bugs before users do and give you confidence to refactor.

Types of Tests

Type What It Tests Speed Coverage
Unit Individual functions Fast Narrow
Integration Components working together Medium Medium
End-to-End Full user flows Slow Broad

The testing pyramid: Lots of unit tests, fewer integration tests, even fewer E2E tests.

Setting Up Jest

npm install -D jest @testing-library/react @testing-library/jest-dom

Create jest.config.js:

module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/$1'
  }
};

Writing Your First Tests

// lib/utils.test.js
import { formatCurrency, calculateDiscount } from './utils';

describe('formatCurrency', () => {
  it('formats positive numbers correctly', () => {
    expect(formatCurrency(1234.56)).toBe('$1,234.56');
  });

  it('handles zero', () => {
    expect(formatCurrency(0)).toBe('$0.00');
  });

  it('handles negative numbers', () => {
    expect(formatCurrency(-50)).toBe('-$50.00');
  });
});

describe('calculateDiscount', () => {
  it('applies percentage discount', () => {
    expect(calculateDiscount(100, 20)).toBe(80);
  });

  it('returns original price for zero discount', () => {
    expect(calculateDiscount(100, 0)).toBe(100);
  });

  it('handles 100% discount', () => {
    expect(calculateDiscount(100, 100)).toBe(0);
  });
});

Testing React Components

// components/Counter.test.js
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';

describe('Counter', () => {
  it('renders initial count', () => {
    render(<Counter initialCount={5} />);
    expect(screen.getByText('Count: 5')).toBeInTheDocument();
  });

  it('increments when + clicked', () => {
    render(<Counter initialCount={0} />);
    fireEvent.click(screen.getByText('+'));
    expect(screen.getByText('Count: 1')).toBeInTheDocument();
  });

  it('decrements when - clicked', () => {
    render(<Counter initialCount={5} />);
    fireEvent.click(screen.getByText('-'));
    expect(screen.getByText('Count: 4')).toBeInTheDocument();
  });
});

Refactoring Patterns

Refactoring is improving code structure without changing behavior.

Extract Function

When code does too many things, extract pieces into separate functions.

Before:

function processOrder(order) {
  // Calculate total
  let total = 0;
  for (const item of order.items) {
    total += item.price * item.quantity;
  }

  // Apply discount
  if (order.discountCode === 'SAVE20') {
    total *= 0.8;
  }

  // Add tax
  total *= 1.08;

  // Format receipt
  return `Order Total: $${total.toFixed(2)}`;
}

After:

function processOrder(order) {
  const subtotal = calculateSubtotal(order.items);
  const afterDiscount = applyDiscount(subtotal, order.discountCode);
  const withTax = addTax(afterDiscount);
  return formatReceipt(withTax);
}

function calculateSubtotal(items) {
  return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}

function applyDiscount(amount, code) {
  const discounts = { 'SAVE20': 0.8, 'SAVE10': 0.9 };
  return amount * (discounts[code] || 1);
}

function addTax(amount, rate = 0.08) {
  return amount * (1 + rate);
}

function formatReceipt(total) {
  return `Order Total: $${total.toFixed(2)}`;
}

Extract Component

When a React component is too large, split it into smaller pieces.

Before (500+ line component):

function Dashboard() {
  // 50 lines of state
  // 100 lines of data fetching
  // 200 lines of handlers
  // 150 lines of JSX
}

After:

function Dashboard() {
  return (
    <DashboardLayout>
      <DashboardHeader />
      <MetricsGrid />
      <RecentActivity />
      <QuickActions />
    </DashboardLayout>
  );
}

Supabase Realtime

Realtime features keep users engaged and create stickiness.

Setting Up Realtime

Enable realtime on your table:

-- Enable realtime for a table
ALTER PUBLICATION supabase_realtime ADD TABLE messages;

Or in the Supabase dashboard: Table Editor → Your Table → Enable Realtime

Subscribing to Changes

import { useEffect, useState } from 'react';
import { supabase } from '../lib/supabase';

function MessageList({ roomId }) {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    // Initial fetch
    async function fetchMessages() {
      const { data } = await supabase
        .from('messages')
        .select('*')
        .eq('room_id', roomId)
        .order('created_at');
      setMessages(data || []);
    }
    fetchMessages();

    // Subscribe to new messages
    const subscription = supabase
      .channel(`room:${roomId}`)
      .on(
        'postgres_changes',
        {
          event: 'INSERT',
          schema: 'public',
          table: 'messages',
          filter: `room_id=eq.${roomId}`
        },
        (payload) => {
          setMessages(prev => [...prev, payload.new]);
        }
      )
      .subscribe();

    // Cleanup on unmount
    return () => {
      subscription.unsubscribe();
    };
  }, [roomId]);

  return (
    <ul>
      {messages.map(msg => (
        <li key={msg.id}>{msg.content}</li>
      ))}
    </ul>
  );
}

Presence: Who’s Online

Show who else is viewing the same content:

function PresenceIndicator({ roomId, currentUser }) {
  const [onlineUsers, setOnlineUsers] = useState([]);

  useEffect(() => {
    const channel = supabase.channel(`presence:${roomId}`);

    channel
      .on('presence', { event: 'sync' }, () => {
        const state = channel.presenceState();
        const users = Object.values(state).flat();
        setOnlineUsers(users);
      })
      .subscribe(async (status) => {
        if (status === 'SUBSCRIBED') {
          await channel.track({
            user_id: currentUser.id,
            user_name: currentUser.name,
            online_at: new Date().toISOString()
          });
        }
      });

    return () => {
      channel.unsubscribe();
    };
  }, [roomId, currentUser]);

  return (
    <div className="flex gap-2">
      {onlineUsers.map(user => (
        <span key={user.user_id} className="px-2 py-1 bg-green-100 rounded">
          {user.user_name}
        </span>
      ))}
    </div>
  );
}

Realtime Use Cases

Feature How It Works
Live chat Subscribe to message inserts
Collaborative editing Broadcast cursor positions with presence
Notifications Subscribe to notifications table
Live dashboards Subscribe to metrics table updates
Activity feeds Subscribe to activity inserts

This Week’s Sprint Work

This is build week, dedicated time to polish and improve.

Focus areas:

  1. Address critical bugs from user testing
  2. Improve onboarding based on where users dropped off
  3. Add at least one test for your core functionality
  4. Clean up one area of tech debt you’ve been avoiding
  5. Optional: Add a realtime feature if it makes sense for your product

Tech Debt Audit:

Use Claude Code to audit your codebase:

claude

Review my codebase and identify: 1. Duplicated code that should be consolidated 2. Large files that should be split 3. Missing error handling 4. Potential security issues 5. Code that’s hard to understand

Prioritize by impact and effort required.

Key Concepts

  • Technical Debt: Cost of rework from choosing quick solutions over better ones
  • Strategic Debt: Deliberate shortcuts with a plan to address them
  • Accidental Debt: Unintentional problems from lack of knowledge
  • Boy Scout Rule: Leave code better than you found it
  • DRY (Don’t Repeat Yourself): Single source of truth for each piece of knowledge
  • Single Responsibility: Each function/component does one thing
  • Linting: Automated code quality checks
  • Formatting: Consistent code style
  • Unit Tests: Testing individual functions
  • Integration Tests: Testing components working together
  • Refactoring: Improving structure without changing behavior
  • Extract Function/Component: Breaking large pieces into smaller ones
  • Supabase Realtime: Live database subscriptions
  • Presence: Tracking who’s currently online