Lesson 03-Data Processing Algorithms

Data Transformation Pipelines

A data transformation pipeline is a sequence of operations that transform raw data into a format suitable for your UI. In frontend, this typically looks like:

API Response → Parse JSON → Filter → Transform → Sort → Group → Render

Each step in the pipeline is an algorithm that transforms the data in some way.

Building a Pipeline with Array Methods

JavaScript’s array methods (map, filter, reduce, sort, groupBy (in newer JS)) are perfect for building data pipelines.

// A complete data processing pipeline
function processUsers(users) {
  return users
    // Step 1: Filter out inactive users
    .filter(user => user.active)
    
    // Step 2: Transform user objects (pick only needed fields)
    .map(user => ({
      id: user.id,
      name: user.name,
      email: user.email,
      department: user.department
    }))
    
    // Step 3: Sort by name
    .sort((a, b) => a.name.localeCompare(b.name))
    
    // Step 4: Group by department
    .reduce((acc, user) => {
      const dept = user.department;
      if (!acc[dept]) acc[dept] = [];
      acc[dept].push(user);
      return acc;
    }, {});
}

// Usage
const users = [
  { id: 1, name: 'Alice', active: true, department: 'engineering' },
  { id: 2, name: 'Bob', active: false, department: 'design' },
  { id: 3, name: 'Charlie', active: true, department: 'engineering' },
  { id: 4, name: 'Diana', active: true, department: 'design' }
];

console.log(processUsers(users));
// {
//   engineering: [{ id: 1, name: 'Alice', ... }, { id: 3, name: 'Charlie', ... }],
//   design: [{ id: 4, name: 'Diana', ... }]
// }

Algorithmic analysis: This pipeline is O(n log n) due to the sort step. The filter, map, and reduce steps are all O(n). If you have n users, the total time complexity is O(n log n).

Optimizing Pipelines: Fusing Operations

Each array method creates a new intermediate array. For large datasets, this can be inefficient. You can fuse multiple operations into a single pass:

// Optimized: single pass, no intermediate arrays
function processUsersOptimized(users) {
  const result = {};
  
  for (const user of users) {
    if (!user.active) continue; // Filter
    
    const transformed = { // Transform
      id: user.id,
      name: user.name,
      email: user.email,
      department: user.department
    };
    
    // Group (we'll sort later)
    const dept = transformed.department;
    if (!result[dept]) result[dept] = [];
    result[dept].push(transformed);
  }
  
  // Sort each group by name
  for (const dept in result) {
    result[dept].sort((a, b) => a.name.localeCompare(b.name));
  }
  
  return result;
}

Tradeoff: The optimized version is faster (single pass, no intermediate arrays), but it’s less readable. Use the pipeline version for small-to-medium datasets, and the optimized version only when performance matters.

Lazy Evaluation with Generators

For very large datasets, you can use generators to process data lazily (one item at a time, without creating intermediate arrays):

// Lazy pipeline using generators
function* filterGen(arr, predicate) {
  for (const item of arr) {
    if (predicate(item)) yield item;
  }
}

function* mapGen(gen, callback) {
  for (const item of gen) {
    yield callback(item);
  }
}

function* sortGen(gen, compareFn) {
  // Collect all items, sort, then yield
  const items = [...gen];
  items.sort(compareFn);
  for (const item of items) {
    yield item;
  }
}

// Usage: lazy pipeline
const users = [/* ... thousands of users ... */];

const pipeline = sortGen(
  mapGen(
    filterGen(users, u => u.active),
    u => ({ id: u.id, name: u.name })
  ),
  (a, b) => a.name.localeCompare(b.name)
);

// Process only the first 10 results (doesn't process the entire array!)
for (let i = 0; i < 10; i++) {
  const { value, done } = pipeline.next();
  if (done) break;
  console.log(value);
}

Algorithmic insight: Lazy evaluation means you don’t process the entire dataset unless you need to. If you only need the first 10 results, a lazy pipeline avoids processing the other 9990 items. This is huge for performance.

Advanced Pipeline Patterns: Chaining and Composition

You can build reusable pipeline components by composing functions:

// Pipeline component: filter by criterion
function filterBy(criterion) {
  return function(arr) {
    return arr.filter(criterion);
  };
}

// Pipeline component: transform by mapping function
function transformBy(mapper) {
  return function(arr) {
    return arr.map(mapper);
  };
}

// Pipeline component: sort by comparator
function sortBy(compareFn) {
  return function(arr) {
    return [...arr].sort(compareFn);
  };
}

// Pipeline component: group by key
function groupBy(keyFn) {
  return function(arr) {
    return arr.reduce((acc, item) => {
      const key = keyFn(item);
      if (!acc[key]) acc[key] = [];
      acc[key].push(item);
      return acc;
    }, {});
  };
}

// Compose pipeline components
function composePipeline(...fns) {
  return function(arr) {
    return fns.reduce((acc, fn) => fn(acc), arr);
  };
}

// Usage: build a reusable pipeline
const processUsers = composePipeline(
  filterBy(u => u.active),
  transformBy(u => ({ id: u.id, name: u.name, dept: u.department })),
  sortBy((a, b) => a.name.localeCompare(b.name)),
  groupBy(u => u.dept)
);

console.log(processUsers(users));

This is a functional programming approach to building data pipelines. Each component is a pure function that transforms an array, and you compose them together.


Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here

Share your love