Gulp streams where Grunt writes temporary files

Both tools drive the same underlying programs, so the choice is not about capability. The difference is that a Grunt task reads files from disk, does one thing and writes them back, so a four-step pipeline is four full passes over the tree with three sets of intermediate output. Gulp passes file objects between steps in memory and touches the disk once, at the end.

// Grunt: concat writes tmp/app.js, uglify reads it back, and the
// order of the two lives in a task list elsewhere in the file
concat: { dist: { src: ['src/**/*.js'], dest: 'tmp/app.js' } },
uglify: { dist: { src: 'tmp/app.js',    dest: 'dist/app.min.js' } }

// Gulp: the pipeline IS the configuration
gulp.task('scripts', function () {
    return gulp.src('src/**/*.js')
        .pipe(concat('app.js'))
        .pipe(uglify())
        .pipe(gulp.dest('dist'));
});

On a few dozen files the saving is a second or two per build and worth changing nothing for. On a Less tree with a thousand partials and a watcher firing on every save it is the difference between a workflow and a wait. What you give up is the declarative form: a Gruntfile is data, and can be read by somebody who does not write JavaScript, whereas a Gulpfile is a program and programs have bugs. Stream error handling is the specific rough edge — an error inside a pipe is emitted on the stream rather than thrown, and if nothing is listening the watch task dies quietly and never rebuilds again. Grunt is two years older, has a plugin for everything, and is still the safer answer for a build somebody else will inherit.