Dropbox Initial Phone Screen

Initial Dropbox Screen

Dropbox - Senior Software Engineer (UI/Frontend) - Initial Screen

Description Template:
Level: IC3
Education: Bachelors
Years of Experience: ~10

It was a single problem that built on itself in 4 total phases. Where the next iteration had you building on your existing code. And if you built things messy, you could be in for a refactor. It was a class and you had methods and state to handle. There was no interviewer and the quiz is timed.

Based on what the recruiter said not all tests are expected to pass in order to move on. It’s rare that people 100% it. I think I got about 95%-ish though.

My problem was for files. Folks online I have seen report variations of this problem. Where it could be keeping track of payroll of workers. Or a simple database state. Or banking.

My final code is below. With some small tweaks and additional test code.

const ACTIONS = {
  ADD_FILE: 'ADD_FILE',
  GET_FILE_SIZE: 'GET_FILE_SIZE',
  MOVE_FILE: 'MOVE_FILE',
  GET_LARGEST_N: 'GET_LARGEST_N',
  GET_VERSION: 'GET_VERSION',
  DELETE_VERSION: 'DELETE_VERSION',
  DELETE_FILES: 'DELETE_FILES',
  RESTORE_FILES: 'RESTORE_FILES',
};

const queries = [
  [ACTIONS.ADD_FILE, '/dir1/dir2/a.txt', '3'], // created
  [ACTIONS.ADD_FILE, '/dir1/b.txt', '1'], // created
  [ACTIONS.DELETE_FILES, '/dir1'], // 2
  [ACTIONS.GET_FILE_SIZE, '/dir1/dir2/a.txt'], // ''
  [ACTIONS.RESTORE_FILES, '/dir1/dir2'], // '1'
  [ACTIONS.GET_FILE_SIZE, '/dir1/dir2/a.txt'], // '3'
];

class FS {
  constructor() {
    // {[name]: { size:number, versions: number[] }}
    this.fs = {};
    this.deleted = {};
  }

  addFile = ([name, size]) => {
    let response = 'created';
    let versions = [size];
    if (this.fs[name]) {
      response = 'overwritten';
      versions = [...this.fs[name].versions, ...versions];
    }
    // DM
    this.fs[name] = { size: Number(size), versions };
    return response;
  };

  getFileSize = ([name]) => {
    if (!this.fs[name]) return '';
    return this.fs[name].size.toString();
  };

  moveFile = ([from, to]) => {
    if (!this.fs[from] || this.fs[to]) return 'false';
    this.fs[to] = this.fs[from];
    delete this.fs[from];
    return 'true';
  };

  getLargest = ([prefix, n]) => {
    let filesList = Object.entries(this.fs);
    filesList = filesList.reduce((acc, [name, file]) => {
      if (name.startsWith(prefix)) {
        acc.push({ name, ...file });
      }
      return acc;
    }, []);
    filesList.sort(
      ({ name: aName, size: aSize }, { name: bName, size: bSize }) => {
        if (aSize === bSize) {
          return aName.localeCompare(bName);
        }
        return bSize - aSize;
      }
    );
    filesList.length = n;
    return filesList
      .reduce((acc, { name, size }) => acc.concat(`${name}(${size})`), [])
      .join(', ');
  };

  getVersion = ([name, version]) => {
    return this.fs[name]?.versions?.[Number(version) - 1]?.toString() || '';
  };

  deleteVersion = ([name, version]) => {
    if (!this.fs[name]) return 'false';
    const versionIndex = Number(version) - 1;
    const { versions } = this.fs[name];
    this.fs[name].versions = versions
      .slice(0, versionIndex)
      .concat(versions.slice(versionIndex + 1));
    if (this.fs[name].versions.length === 0) {
      delete this.fs[name];
    }
    return 'true';
  };

  deleteFiles = ([prefix]) => {
    const entires = Object.entries(this.fs);
    let count = 0;
    entires.forEach(([name, file]) => {
      if (name.startsWith(prefix)) {
        delete this.fs[name];
        this.deleted[name] = file;
        count++;
      }
    });
    return count.toString();
  };

  restoreFiles = ([prefix]) => {
    const entires = Object.entries(this.deleted);
    let count = 0;
    entires.forEach(([name, file]) => {
      if (name.startsWith(prefix)) {
        delete this.deleted[name];
        this.fs[name] = file;
        count++;
      }
    });
    return count.toString();
  };
}

const fs = new FS();

const log = (str) => console.log(JSON.stringify(str));

const runQueries = (queries) => {
  queries.forEach((query) => {
    const [action, ...params] = query;
    if (action === ACTIONS.ADD_FILE) log(fs.addFile(params));
    if (action === ACTIONS.GET_FILE_SIZE) log(fs.getFileSize(params));
    if (action === ACTIONS.MOVE_FILE) log(fs.moveFile(params));
    if (action === ACTIONS.GET_LARGEST_N) log(fs.getLargest(params));
    if (action === ACTIONS.GET_VERSION) log(fs.getVersion(params));
    if (action === ACTIONS.DELETE_VERSION) log(fs.deleteVersion(params));
    if (action === ACTIONS.DELETE_FILES) log(fs.deleteFiles(params));
    if (action === ACTIONS.RESTORE_FILES) log(fs.restoreFiles(params));
  });
};
runQueries(queries);